A clear explanation of minimizing total travel cost for two-city scheduling using a greedy refund approach after sending everyone to city A.
Problem Restatement
We have 2n people who must each travel to one of two cities: city A or city B.
Exactly n people must go to city A and exactly n must go to city B.
The cost to send person i to city A is costs[i][0] and to city B is costs[i][1].
We need to return the minimum total cost.
The official constraints state that 2 <= costs.length <= 200, costs.length is even, and 1 <= costs[i][0], costs[i][1] <= 10^4.
Input and Output
| Item | Meaning |
|---|---|
| Input | Array costs where costs[i] = [aCost, bCost] |
| Output | Minimum total cost to send n people to each city |
Function shape:
def twoCitySchedCost(costs: list[list[int]]) -> int:
...Examples
Example 1:
costs = [[10,20],[30,200],[400,50],[30,20]]Send persons 0, 1 to A: 10 + 30 = 40. Send persons 2, 3 to B: 50 + 20 = 70. Total: 110.
Answer:
110Key Insight
Start by sending everyone to city A. Cost = sum(costs[i][0]).
Now we need to switch n people to city B. Switching person i from A to B costs an extra:
costs[i][1] - costs[i][0]This is the “refund” (can be negative, meaning cheaper).
To minimize total cost, sort by this difference and switch the n people with the smallest differences.
Algorithm
- Compute base cost: send everyone to A.
- Compute
diff[i] = costs[i][1] - costs[i][0]for each person. - Sort
diff. - Add the
nsmallest differences to the base cost. - Return total.
Correctness
Sending everyone to A gives the base cost. Switching person i to B changes the cost by costs[i][1] - costs[i][0].
We must switch exactly n people. To minimize the total change, pick the n people with the smallest switching cost (most negative differences benefit us most).
Edge Cases
- Check the minimum input size allowed by the constraints.
- Verify duplicate values or tie cases if the input can contain them.
- Keep the return value aligned with the exact failure case in the statement.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n log n) | Sorting |
| Space | O(1) | Sort in place |
Common Pitfalls
- Do not optimize away the invariant; the code should still make it clear what condition is being maintained.
- Prefer problem-specific names over one-letter variables once the logic becomes stateful.
Implementation
class Solution:
def twoCitySchedCost(self, costs: list[list[int]]) -> int:
costs.sort(key=lambda c: c[1] - c[0])
n = len(costs) // 2
return sum(costs[i][0] for i in range(n)) + sum(costs[i][1] for i in range(n, 2 * n))Code Explanation
Sort by the cost difference (benefit of going to B vs A):
costs.sort(key=lambda c: c[1] - c[0])Those with the smallest (most negative) difference benefit most from going to B.
Send the first n (cheapest to redirect to B) to B, and the last n to A:
sum(costs[i][0] for i in range(n)) + sum(costs[i][1] for i in range(n, 2 * n))Wait — those sorted first (smallest b - a) are cheapest to send to B. So send them to B and the rest to A.
After sorting by costB - costA, the first n people are cheapest to send to city B relative to city A, and the remaining n people go to city A. Therefore:
- First
n: send to B →costs[i][1] - Last
n: send to A →costs[i][0]
Testing
def run_tests():
s = Solution()
assert s.twoCitySchedCost([[10,20],[30,200],[400,50],[30,20]]) == 110
assert s.twoCitySchedCost([[259,770],[448,54],[926,667],[184,139],[840,118],[577,469]]) == 1859
print("all tests passed")
run_tests()| Test | Expected | Why |
|---|---|---|
| 4-person example | 110 | Optimal assignment splits costs |
| 6-person example | 1859 | Greedy refund approach |