LeetCode 1029中等贪心
两地调度 图解题解
这道题到底在问什么
有 2N 个人,第 i 个人去 A 城花费 costs[i][0]、去 B 城花费 costs[i][1]。必须恰好 N 人去 A、N 人去 B,求最小总费用。
- costs
- [[10,20],[30,200],[400,50],[30,20]]
- N
- 2(各去 2 人)
- 输出
- 110
最优解:一步一步想明白
- 3关键问题:名额有限,该让谁去 A?衡量标准是每个人的 aCost − bCost:这个差越小(越负),说明他去 A 比去 B 划算得越多,就越该占 A 的名额。
- 4于是策略定型:按 aCost − bCost 升序排序,排在前一半的人去 A(他们去 A 最赚),后一半去 B。下面这排格子放的就是每个人的差值 aCost − bCost。
- 5差值数组,准备排序格子里是每个人的 aCost − bCost:p0=−10、p1=−170、p2=+350、p3=+10。负得越多越该去 A,正得越多越该去 B。现在把它们从小到大排序。
- 6指针看 −10,先记它为候选最小排序就像每次挑出还没排好的最小值放到最前。先把位置 0 的 −10 当作候选最小,指针往右一格格比。
- 7−170 < −10,更新候选最小指针挪到位置 1,−170 < −10,更小!候选最小(绿框)更新成 −170(p1 去 A 比去 B 省 170,最划算)。
- 8继续扫位置 2、3指针继续扫到位置 2(350)、位置 3(+10),都比 −170 大,候选最小不变。一趟扫完确认 −170 是全场最小。
- 9−170 ↔ −10把 −170 和位置 0 的 −10 交换。现在位置 0 是 −170,锁定不动(变绿)。
- 10扫描位置 1~3:−10 最小继续在还没排好的 −10、350、+10 里挑最小。−10 最小,而它正好已经在位置 1,不用换。
- 11位置 1 排好位置 1 锁定为 −10。已经排好的前两个是 −170、−10,正是最该去 A 的两个人。
- 12候选最小 = 350前两位已锁定,未排好区从位置 2 开始。先把 350 记为候选最小,继续往右比。
- 13扫描位置 3:+10 < 350指针到位置 3,+10 < 350,更小!最小是 +10(在位置 3),接下来把它换到位置 2。
- 14+10 ↔ 350交换后位置 2 是 +10,锁定。最后剩 350 自然落在位置 3。
- 15全部升序排好排序完成:−170, −10, +10, +350。对应的人依次是 p1、p0、p3、p2。越靠前的人去 A 越划算,接下来前一半去 A、后一半去 B。
- 16位置 0、1 → A 城N = 总人数 ÷ 2 = 2。排在前 2 位的 p1、p0 去 A(绿色),后 2 位暂时灰着,他们要去 B。
- 17total = 0 → 30第一个去 A 的是 p1,加上他的 aCost = 30。总费用 total = 30。
- 18total = 30 → 40第二个去 A 的是 p0,加上他的 aCost = 10。总费用累加到 total = 40。A 城名额满了。
- 19前一半锁定,A 组 = 30+10A 城两个名额已满,A 组小计 40(30 + 10)。接下来把后一半的人都送去 B。
- 20位置 2、3 → B 城剩下的 p3、p2 去 B(蓝色)。他们差值为正,本就去 B 更省。开始累加他们的 bCost。
- 21total = 40 → 60p3 去 B,加上他的 bCost = 20。总费用 total = 60。
- 22total = 60 → 110最后 p2 去 B,加上 bCost = 50。总费用累加到 total = 110。所有人都分配完了。
- 23A:{p1,p0}=40 + B:{p3,p2}=70 = 110 ✓答案 110!绿色去 A(40)、蓝色去 B(70)。按差值排序、前一半去 A,就拿到了全局最优。
- 24为什么排序就一定最优?因为每个人去 A 相对去 B 的「净省」就是 −(aCost−bCost),要让 N 个去 A 的人净省最多,就该挑差值最小的 N 个——这正是排序前一半。
- 28错误键:按 aCost 升序假如按 aCost 排序:前两小是 p0(10)、p1(30) 去 A=40,p3、p2 去 B=70,这例恰好也得 110;但换一组数据(如有人 aCost 小却 bCost 更小)就会选错——必须用差值才稳。
⚠️ 容易写错的地方
✗ 错:按 aCost 或 bCost 单独排序
✓ 对:必须按差值 aCost − bCost 排序
单看一城的价格无法衡量「去 A 比去 B 划算多少」,会选错人
✗ 错:贪心地每人各选更便宜的城
✓ 对:差值排序后强制前一半 A、后一半 B
各选最便宜会破坏「各 N 人」的名额约束,可能 A 城超员
完整代码(Python / C++ / Java)
Python
class Solution:
def twoCitySchedCost(self, costs):
costs.sort(key=lambda c: c[0] - c[1]) # 按 aCost-bCost 升序
n = len(costs) // 2
total = 0
for i in range(len(costs)):
total += costs[i][0] if i < n else costs[i][1] # 前一半去A,后一半去B
return totalC++
class Solution {
public:
int twoCitySchedCost(vector<vector<int>>& costs) {
sort(costs.begin(), costs.end(), [](const vector<int>& a, const vector<int>& b){
return a[0] - a[1] < b[0] - b[1];
});
int n = costs.size() / 2, total = 0;
for (int i = 0; i < (int)costs.size(); i++) {
total += (i < n) ? costs[i][0] : costs[i][1];
}
return total;
}
};Java
class Solution {
public int twoCitySchedCost(int[][] costs) {
Arrays.sort(costs, new Comparator<int[]>() {
public int compare(int[] a, int[] b) {
return (a[0] - a[1]) - (b[0] - b[1]);
}
});
int n = costs.length / 2, total = 0;
for (int i = 0; i < costs.length; i++) {
total += (i < n) ? costs[i][0] : costs[i][1];
}
return total;
}
}复杂度
时间复杂度
O(n log n)
主要开销在排序;之后一次线性遍历累加是 O(n),整体由排序主导
空间复杂度
O(1)
原地排序 + 几个累加变量;不计排序内部栈空间则为常数额外空间
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 两地调度 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么贪心(差值排序)一定正确?+
以全去 B 为基准,改某人去 A 使总费用增加 aCost−bCost;要让增量之和最小且恰好 N 人去 A,就取差值最小的 N 个,即升序前一半。这是一个可严格证明的交换论证。
能不能用动态规划?+
可以。dp[i][j] = 前 i 人中 j 人去 A 的最小费用,转移取「第 i 人去 A 或去 B」。时间 O(n²),不如贪心的 O(n log n) 优,但思路更通用。
如果 A、B 名额不等(如 A 去 K 人)?+
同样按差值升序排,取前 K 个去 A、其余去 B 即可,贪心论证不变。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 两地调度 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。