LeetCode 1029 - Two City Scheduling


https://leetcode.com/problems/two-city-scheduling/
There are 2N people a company is planning to interview. The cost of flying the i-th person to city A is costs[i][0], and the cost of flying the i-th person to city B is costs[i][1].
Return the minimum cost to fly every person to a city such that exactly N people arrive in each city.

Example 1:
Input: [[10,20],[30,200],[400,50],[30,20]]
Output: 110
Explanation: 
The first person goes to city A for a cost of 10.
The second person goes to city A for a cost of 30.
The third person goes to city B for a cost of 50.
The fourth person goes to city B for a cost of 20.

The total minimum cost is 10 + 30 + 50 + 20 = 110 to have half the people interviewing in each city.

Note:
  1. 1 <= costs.length <= 100
  2. It is guaranteed that costs.length is even.
  3. 1 <= costs[i][0], costs[i][1] <= 1000

Greedy
Sort by cost_a – cost_b, Choose the first n/2 people for A, rest for B
https://leetcode.com/problems/two-city-scheduling/discuss/278716/C%2B%2B-O(n-log-n)-sort-by-savings
How much money can we save if we fly a person to A vs. B? To minimize the total cost, we should fly the person with the maximum saving to A, and with the minimum - to B.
Example: [30, 100], [40, 90], [50, 50], [70, 50].
Savings: 70, 50, 0, -20.
Obviously, first person should fly to A, and the last - to B.

Solution

We sort the array by the difference between costs for A and B. Then, we fly first N people to A, and the rest - to B.
int twoCitySchedCost(vector<vector<int>>& cs, int res = 0) {
  sort(begin(cs), end(cs), [](vector<int> &v1, vector<int> &v2) {
    return (v1[0] - v1[1] < v2[0] - v2[1]);
  });
  for (auto i = 0; i < cs.size() / 2; ++i) {
    res += cs[i][0] + cs[i + cs.size() / 2][1];
  }
  return res;
}

Optimized Solution

Actually, we do not need to perfectly sort all cost differences, we just need the biggest savings (to fly to A) to be in the first half of the array. So, we can use the quick select algorithm (nth_element in C++) and use the middle of the array as a pivot.
This brings the runtime down from 8 ms to 4 ms (thanks @popeye1 for the tip!)
int twoCitySchedCost(vector<vector<int>>& cs, int res = 0) {
  nth_element(begin(cs), begin(cs) + cs.size() / 2, end(cs), [](vector<int> &a, vector<int> &b) {
    return (a[0] - a[1] < b[0] - b[1]);
  });
  for (auto i = 0; i < cs.size() / 2; ++i) {
    res += cs[i][0] + cs[i + cs.size() / 2][1];
  }
  return res;
}

Complexity Analysis

Runtime: O(n log n). We sort the array then go through it once. The second solution has a better average case runtime.
Memory: O(1). We sort the array in-place

https://leetcode.com/problems/two-city-scheduling/discuss/278898/Java-2ms-sorting-solution-with-explanation
The cost of sending a flyer to city B is relative to the cost of sending them to city A. If we send them to city B, then we have to send some other flyer to city A because we need N people in both cities. We could generally find the highest costs to B and send those people to A, but if they also have a high cost to A, then we didn't save much. Similarly, if we find the lowest costs to B and send those people to B, then we might not have saved much money over sending them to A - and meanwhile if that action caused us to send someone to A who cost us a lot more, we've lost money overall.
Another way to look at it is that each person costs a certain amount to fly regardless of the city they go to, then they cost an additional premium to fly to one of the cities over the other. If their cost pair is [1000,1001] basically that person costs 1000 no matter what and we are only looking at saving or spending that extra dollar. We could reduce the solution by subtracting the minimum cost from both sides of each pair and then looking at optimizing the differential costs. That person's costs would then be fixed=1000, relative=[0,1]. It produces the same answer, but it seems simpler because now everyone has a 0 (relative) cost for one city and a non-zero cost to the other. The solution at this point would be fairly simple - send the people with the largest differential costs to the city of their 0 relative cost, and then when you get the people with large differences assigned you end up with a lot of people with small differences, you keep doing this, saving less and less each time until you might end up with a bunch of people who all cost extra money to send to one city, but you've already assigned everyone you need to the other city. For example, the cost pairs [10,5], [10,7], [10,8], [5,10] could be made differential and you would get the costs fixed = [5,7,8,5], differentials=[[5,0],[3,0],[2,0],[0,5]] You know it will cost you at least 5+7+8+5 == 25 to send everyone, but how can you save the remaining costs? You obviously send the first person to city B and the last to city A and now you have one more for each city and the remainig differential costs are [3,0],[2,0]. You can't send them both to city B since that would leave the cities improperly staffed, so you have to pick one to send to A and the other to B. Clearly you send the one that costs the least extra to send to A which is the latter of the two.
Subtracting out the fixed costs really makes it rather obvious who to send where, you just sort them in the order from most costly to send to A, to most costly to send to B and then send the first half to city B and the second half to city A. It seems odd to have a dual-sort where one value is decreasing while the other is increasing, but since all cost pairs have one zero and one non-zero it is pretty obvious that the sorted order starts with the pairs that have a zero differential cost for B and they are in order of decreasing relative cost to send to A, then that is followed by all of the people who have a zero differential cost to go to A in the order of increasing cost to send them to B. It would look something like this: [10,0],[5,0],[3,0],...[0,2],[0,5],[0,100] It's pretty clear that the first person goes to B and the last one goes to A and choosing the first half of the list to go to B would minimize the relative costs for city B and the latter half would minimize the relative costs to go to A.
But, you don't need to actually extract the fixed costs, you can do this with the original amounts just by looking at the difference between the numbers. Creating a bunch of relative costs just gives you a bunch of pairs of numbers were one of them is 0. If you subtract the A cost from the B cost, you get a single number that sorts the people by the relative cost to send them to B. You then send the ones with the highest relative B cost to A and vice versa.
Thus the technique here, sort the list first by the B-A cost, then the beginning of the list (smallest values of "relative cost to send to B") are the ones you would rather send to B and the ones at the end you'd rather send to A.
    public int twoCitySchedCost(int[][] costs) {
        Arrays.sort(costs, new Comparator<int[]>() {
            public int compare(int[] a, int[] b) {
                return (a[1] - a[0]) - (b[1] - b[0]);
            }
        });
        int cost = 0;
        for (int i = 0; i < costs.length / 2; i++) {
            cost += costs[i][1] + costs[costs.length-i-1][0];
        }
        return cost;
    }
https://leetcode.com/problems/two-city-scheduling/discuss/278816/Java-Sort-O(NlogN)
public int twoCitySchedCost(int[][] costs) {
        Arrays.sort(costs, new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                return a[0] - a[1] - (b[0] - b[1]);
            }
        });
        int res = 0;
        for (int i = 0; i < costs.length / 2; i++) {
            res += costs[i][0];
        }
        for (int i = costs.length / 2; i < costs.length;i++) {
            res += costs[i][1];
        }
        return res;
    }

X. DP
https://zxi.mytechroad.com/blog/tag/contest/
dp[i][j] := min cost to put j people into city A for the first i people
dp[0][0] = 0
dp[i][0] = dp[i -1][0] + cost_b
dp[i][j] = min(dp[i – 1][j] + cost_b, dp[i – 1][j – 1] + cost_a)
ans := dp[n][n/2]

Time complexity: O(n^2)
Space complexity: O(n^2)

https://leetcode.com/problems/two-city-scheduling/discuss/278731/Java-DP-Easy-to-Understand
dp[i][j] represents the cost when considering first (i + j) people in which i people assigned to city A and j people assigned to city B.
my explanation here, just in case other people need this:
for (i+j)th people, he/she can be assigned either to A city or B city,
the min cost if he is assigned to A city: dp[i-1][j]+costs[i+j-1][0]; //because it is to A, so we should use i-1
the min cost if he is assigned to B city: dp[i][j-1]+costs[i+j-1][1]; //because it is to B, so we should use j-1
so dp[i][j] = Math.min(dp[i-1][j]+costs[i+j-1][0] , dp[i][j-1]+costs[i+j-1][1]);
another way to represent the dp equation is: dp[totalPerson][personToA], toatalPerson is the number of people have been assigned, and personToA of them are assigned to city A, so the the equation:
dp[totalPerson][personToA]= Math.min(dp[totalPerson-1][personToA]+costs[totalPerson-1][1], //the last one to B
dp[totalPerson-1][personToA-1]+costs[totalPerson-1][0]);//the last one to A
    public int twoCitySchedCost(int[][] costs) {
        int N = costs.length / 2;
        int[][] dp = new int[N + 1][N + 1];
        for (int i = 1; i <= N; i++) {
            dp[i][0] = dp[i - 1][0] + costs[i - 1][0];
        }
        for (int j = 1; j <= N; j++) {
            dp[0][j] = dp[0][j - 1] + costs[j - 1][1];
        }
        for (int i = 1; i <= N; i++) {
            for (int j = 1; j <= N; j++) {
                dp[i][j] = Math.min(dp[i - 1][j] + costs[i + j - 1][0], dp[i][j - 1] + costs[i + j - 1][1]);
            }
        }
        return dp[N][N];
    }
X. DFS+Cache
https://leetcode.com/problems/two-city-scheduling/discuss/278799/Java-Easy-to-understand-DFS-with-cache-solution
    public int twoCitySchedCost(int[][] costs) {
        int len = costs.length / 2;
        int[][] memo = new int[len + 1][len + 1];
        dfs(memo, costs, len, len, 0);
        return memo[len][len];
    }
    
    private int dfs(int[][] memo, int[][] costs, int A, int B, int index) {
        if (index == costs.length) {
            return 0;
        }
        
        if (memo[A][B] != 0) {
            return memo[A][B];
        }
        
        int costA = costs[index][0];
        int costB = costs[index][1];
        
        if (A == 0 && B > 0) {
            memo[A][B] = costB + dfs(memo, costs, A, B - 1, index + 1);
        } else if (B == 0 && A > 0) {
            memo[A][B] = costA + dfs(memo, costs, A - 1, B, index + 1);
        } else {
            int sumA = costA + dfs(memo, costs, A - 1, B, index + 1);
            int sumB = costB + dfs(memo, costs, A, B - 1, index + 1);
            memo[A][B] = Math.min(sumA, sumB);
        }
        
        return memo[A][B];
    }

https://www.acwing.com/solution/LeetCode/content/1758/

公司计划面试 2N 人。第 i 人飞往 A 市的费用为 costs[i][0],飞往 B 市的费用为 costs[i][1]。
返回将每个人都飞到某座城市的最低费用,要求每个城市都有 N 人抵达。

样例

示例:

输入:[[10,20],[30,200],[400,50],[30,20]]
输出:110
解释:
第一个人去 A 市,费用为 10。
第二个人去 A 市,费用为 30。
第三个人去 B 市,费用为 50。
第四个人去 B 市,费用为 20。

最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。


提示:

1 <= costs.length <= 100
costs.length 为偶数
1 <= costs[i][0], costs[i][1] <= 1000
(排序) O(nlogn)
这道题先假设所有的人都去A市,然后我们只需要选取去B市的费用与去A市的费用的差最小的N个人改去B市即可。
时间复杂度分析:排序时间复杂度O(nlogn)


  1. 输入一个长度是2*N的二维数组, a[i][0]表示第i个人分配到A城市的价格, a[i][1]表示第i个人分配到B城市的价格
  2. 把这2*N个数组分成长度相等的两份, 其中一半选择A, 另外一半选择B, 问总共最小的总共价格是多少?
刷题感悟
  1. 这个题目暴力肯定不行, 直接简单的贪心选择最小的也不对, 卡壳了好一会儿。
    还好后来终于想出来了,感觉这样的题目能不能分析出来很没有把握。
解题思路分析
  1. 假设所有的人都选择城市A, 这时候sum=sum{a[i][0]},
    然后要选择一半的人改成B, 这个时候, 选择某一个人对sum的影响是d=a[i][1]-a[i][0],
    那么, 我们要让结果最小, 就需要让这个d最小, 那按照这个d对数组排序,然后选择最小的一半就好

public int twoCitySchedCost(int[][] costs) {
    Arrays.sort(costs, new Comparator<int[]>() {
        public int compare(int[] a, int[] b) {
            int v1 = a[1] - a[0];    
            int v2 = b[1] - b[0];
            return v1-v2;
        }
    });
    int sum = 0;
    for(int[] cost: costs) {
        sum += cost[0];
    }
    for(int i = 0; i < costs.length/2; ++i) {
        sum += costs[i][1] - costs[i][0];
    }
    return sum;
}
X. Video
花花酱 LeetCode Weekly Contest 133 (1029,1030,1031,1032)



Leetcode 1029 - Two City Scheduling | Greedy Algo | Company Interview Tags: Bloomberg

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts