1029. Two City Scheduling
Problem Description
You have 2n
people that need to be interviewed, and the company wants to conduct interviews in two different cities (city A and city B). Each person must be flown to exactly one city for their interview.
For each person i
, you're given the costs to fly them to each city:
aCost[i]
- the cost to fly personi
to city AbCost[i]
- the cost to fly personi
to city B
These costs are provided in an array costs
where costs[i] = [aCost[i], bCost[i]]
.
The constraint is that you must send exactly n
people to city A and exactly n
people to city B (splitting the 2n
people evenly between the two cities).
Your task is to find the minimum total cost to fly all 2n
people to their assigned cities while maintaining this even split.
For example, if you have 4 people (2n = 4
, so n = 2
) with costs:
- Person 0:
[10, 20]
(costs 20 to city B) - Person 1:
[30, 200]
(costs 200 to city B) - Person 2:
[400, 50]
(costs 50 to city B) - Person 3:
[30, 20]
(costs 20 to city B)
You need to choose which 2 people go to city A and which 2 go to city B to minimize the total cost.
Intuition
The key insight is to think about the relative cost difference between sending each person to city A versus city B. For each person, we can calculate how much we "save" or "lose" by choosing city A over city B.
Consider this: if we send person i
to city A instead of city B, the relative cost difference is costs[i][0] - costs[i][1]
.
- If this value is negative, it means city A is cheaper for this person
- If this value is positive, it means city B is cheaper for this person
For example, if person i
costs [10, 30]
, the difference is 10 - 30 = -20
, meaning we save $20 by sending them to city A instead of city B.
Now, since we must send exactly n
people to each city, we want to:
- Send the
n
people who have the most negative (or least positive) difference to city A - Send the remaining
n
people to city B
Why does this work? By sorting people based on costs[i][0] - costs[i][1]
, we're arranging them from those who benefit most from going to city A (large negative differences) to those who benefit least (or actually cost more to go to city A, with positive differences).
After sorting by this difference:
- The first
n
people in the sorted list are the ones where city A is relatively cheapest (or least expensive compared to B) - The last
n
people are the ones where city B is relatively cheapest
This greedy approach ensures we're making the most cost-effective assignment while respecting the constraint of sending exactly n
people to each city. We're essentially maximizing our "savings" by sending each person to their relatively cheaper destination whenever possible, within the constraints.
Solution Approach
The implementation follows a greedy approach using sorting based on cost differences:
-
Sort by Cost Difference: First, we sort the entire
costs
array based on the difference between city A cost and city B cost for each person:costs.sort(key=lambda x: x[0] - x[1])
This lambda function
x[0] - x[1]
calculates the relative cost of sending each person to city A versus city B. After sorting, people who should preferably go to city A (negative or small differences) appear first. -
Calculate n: We determine how many people should go to each city:
n = len(costs) >> 1
The bit shift operation
>> 1
is equivalent to dividing by 2, giving usn
(half of2n
people). -
Assign Cities and Calculate Total Cost: After sorting, we use a simple strategy:
- Send the first
n
people to city A (these have the smallestA - B
differences) - Send the last
n
people to city B
The total cost calculation is done in one line:
return sum(costs[i][0] + costs[i + n][1] for i in range(n))
Breaking this down:
costs[i][0]
fori
in range[0, n)
โ cost of sending firstn
people to city Acosts[i + n][1]
fori
in range[0, n)
โ cost of sending people at indices[n, 2n)
to city B
- Send the first
Time Complexity: O(n log n)
due to sorting, where n
is the total number of people (note: the problem uses 2n
people, so technically O(2n log 2n)
which simplifies to O(n log n)
).
Space Complexity: O(1)
if we don't count the space used by the sorting algorithm (which is typically O(log n)
for the recursion stack in languages like Python).
The elegance of this solution lies in recognizing that sorting by the cost difference automatically groups people by their optimal city assignment while respecting the constraint of equal distribution.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with 4 people (2n = 4, so n = 2):
Given costs:
- Person 0:
[10, 20]
(costs 20 to city B) - Person 1:
[30, 200]
(costs 200 to city B) - Person 2:
[400, 50]
(costs 50 to city B) - Person 3:
[30, 20]
(costs 20 to city B)
Step 1: Calculate cost differences (A - B) for each person
- Person 0:
10 - 20 = -10
(saving $10 by choosing city A) - Person 1:
30 - 200 = -170
(saving $170 by choosing city A) - Person 2:
400 - 50 = 350
(losing $350 by choosing city A) - Person 3:
30 - 20 = 10
(losing $10 by choosing city A)
Step 2: Sort people by their cost differences (ascending) After sorting by difference:
- Person 1:
[30, 200]
with difference = -170 - Person 0:
[10, 20]
with difference = -10 - Person 3:
[30, 20]
with difference = 10 - Person 2:
[400, 50]
with difference = 350
Step 3: Assign cities based on sorted order
- First n=2 people go to city A:
- Person 1 โ city A: cost = $30
- Person 0 โ city A: cost = $10
- Last n=2 people go to city B:
- Person 3 โ city B: cost = $20
- Person 2 โ city B: cost = $50
Step 4: Calculate total cost Total = 30 + 10 + 20 + 50 = $110
This assignment makes sense because we sent the people with the most negative differences (who benefit most from city A) to city A, and the people with positive differences (who prefer city B) to city B, achieving the minimum total cost while maintaining the n/n split.
Solution Implementation
1class Solution:
2 def twoCitySchedCost(self, costs: List[List[int]]) -> int:
3 # Sort people by the difference between cost to fly to city A vs city B
4 # Those with larger savings when flying to A (negative difference) come first
5 costs.sort(key=lambda x: x[0] - x[1])
6
7 # Calculate half the total number of people
8 # Using bit shift right by 1 is equivalent to dividing by 2
9 n = len(costs) >> 1
10
11 # Send first n people to city A (costs[i][0])
12 # Send remaining n people to city B (costs[i + n][1])
13 # This minimizes total cost due to our sorting strategy
14 return sum(costs[i][0] + costs[i + n][1] for i in range(n))
15
1class Solution {
2 public int twoCitySchedCost(int[][] costs) {
3 // Sort the costs array based on the difference between flying to city A vs city B
4 // The difference (costA - costB) represents how much more expensive city A is compared to city B
5 // Sorting in ascending order puts people who benefit most from going to city A at the beginning
6 Arrays.sort(costs, (a, b) -> {
7 int differenceA = a[0] - a[1]; // How much more expensive city A is for person a
8 int differenceB = b[0] - b[1]; // How much more expensive city A is for person b
9 return differenceA - differenceB;
10 });
11
12 // Initialize total cost
13 int totalCost = 0;
14
15 // Calculate n as half of the total number of people
16 int n = costs.length / 2;
17
18 // Send first n people to city A (those who benefit most from going to A)
19 // Send last n people to city B (those who benefit most from going to B)
20 for (int i = 0; i < n; i++) {
21 totalCost += costs[i][0]; // Add cost for person i to go to city A
22 totalCost += costs[i + n][1]; // Add cost for person (i+n) to go to city B
23 }
24
25 return totalCost;
26 }
27}
28
1class Solution {
2public:
3 int twoCitySchedCost(vector<vector<int>>& costs) {
4 // Sort people by the difference between flying to city A vs city B
5 // Those with larger savings by choosing city A will be at the front
6 sort(costs.begin(), costs.end(), [](const vector<int>& personA, const vector<int>& personB) {
7 // Calculate the relative cost difference for each person
8 // (costA - costB) represents how much more/less it costs to send to city A
9 // Negative values mean city A is cheaper, positive means city B is cheaper
10 return (personA[0] - personA[1]) < (personB[0] - personB[1]);
11 });
12
13 // Total number of people is even, split equally between two cities
14 int halfSize = costs.size() / 2;
15 int totalCost = 0;
16
17 // Send first half to city A (those who save most by going to A)
18 for (int i = 0; i < halfSize; ++i) {
19 totalCost += costs[i][0];
20 }
21
22 // Send second half to city B (those who save most by going to B)
23 for (int i = halfSize; i < costs.size(); ++i) {
24 totalCost += costs[i][1];
25 }
26
27 return totalCost;
28 }
29};
30
1/**
2 * Calculates minimum cost to fly every person to one of two cities
3 * Each person must fly to exactly one city, and exactly n people must fly to each city
4 * @param costs - 2D array where costs[i] = [costToCity_A, costToCity_B] for person i
5 * @returns Minimum total cost to fly all people to the two cities
6 */
7function twoCitySchedCost(costs: number[][]): number {
8 // Sort by the difference between flying to city A vs city B
9 // People with larger savings when choosing city A over city B come first
10 costs.sort((personA: number[], personB: number[]) => {
11 const savingsPersonA: number = personA[0] - personA[1];
12 const savingsPersonB: number = personB[0] - personB[1];
13 return savingsPersonA - savingsPersonB;
14 });
15
16 // Calculate half the total number of people (n people go to each city)
17 const halfPeople: number = costs.length >> 1;
18
19 // Initialize total cost
20 let totalCost: number = 0;
21
22 // First half of sorted array goes to city A (best savings for city A)
23 // Second half goes to city B
24 for (let i: number = 0; i < halfPeople; ++i) {
25 totalCost += costs[i][0]; // Person i goes to city A
26 totalCost += costs[i + halfPeople][1]; // Person (i + n) goes to city B
27 }
28
29 return totalCost;
30}
31
Time and Space Complexity
Time Complexity: O(n log n)
where n
is the total number of people (length of the costs array).
- The sorting operation
costs.sort(key=lambda x: x[0] - x[1])
dominates the time complexity, which takesO(n log n)
time. - The subsequent sum operation with list comprehension iterates through
n/2
elements, takingO(n/2)
=O(n)
time. - Overall:
O(n log n) + O(n)
=O(n log n)
Space Complexity: O(1)
or O(n)
depending on the sorting algorithm implementation.
- The sort is performed in-place on the input array, so no additional space is needed for storing a separate sorted array.
- The list comprehension in the sum creates a generator expression which uses
O(1)
space. - However, Python's Timsort (used in
.sort()
) may use up toO(n)
auxiliary space in the worst case for its merge operations. - If we consider only the extra space explicitly allocated by the algorithm (excluding sort's internal space), it's
O(1)
. - If we include the sorting algorithm's space requirements, it's
O(n)
in the worst case.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Sorting Logic
A common mistake is incorrectly implementing the sorting comparator. Some developers might sort by x[1] - x[0]
instead of x[0] - x[1]
, which would reverse the optimal assignment.
Wrong approach:
costs.sort(key=lambda x: x[1] - x[0]) # This sorts in the wrong order!
Why it matters: The difference x[0] - x[1]
represents how much MORE it costs to send someone to city A compared to city B. A negative value means city A is cheaper for that person. By sorting in ascending order of this difference, we prioritize people who save the most money by going to city A.
2. Off-by-One Errors in Index Calculation
When calculating the sum, developers might make indexing errors:
Common mistakes:
# Mistake 1: Using wrong indices for city B
return sum(costs[i][0] + costs[n + i][1] for i in range(n)) # Should be costs[i + n][1]
# Mistake 2: Incorrect range
return sum(costs[i][0] for i in range(n)) + sum(costs[i][1] for i in range(n, len(costs)))
# While this works, it's less elegant and more prone to errors
3. Not Handling Edge Cases
Although the problem guarantees 2n
people, defensive programming suggests checking:
Improved solution with validation:
class Solution:
def twoCitySchedCost(self, costs: List[List[int]]) -> int:
if not costs or len(costs) % 2 != 0:
return 0 # or raise an exception
costs.sort(key=lambda x: x[0] - x[1])
n = len(costs) >> 1
return sum(costs[i][0] + costs[i + n][1] for i in range(n))
4. Overthinking with Dynamic Programming
Some developers might immediately reach for DP when they see an optimization problem with constraints. While DP can solve this problem, it's unnecessarily complex:
Overcomplicated DP approach (avoid this):
# This works but is overkill - O(nยฒ) space and time
dp = {}
def solve(i, a_count):
if i == len(costs):
return 0 if a_count == n else float('inf')
if (i, a_count) in dp:
return dp[(i, a_count)]
# ... more complex DP logic
The greedy approach is both simpler and more efficient for this specific problem structure.
Which data structure is used to implement recursion?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!