LeetCode 1049中等0-1 背包
最后一块石头 II 图解题解
这道题到底在问什么
每次取两块石头相撞,重的减轻的、剩下的留下;求最后剩下那块(或 0)的最小可能重量。
- 输入
- stones=[2,4,1,5,3]
- 输出
- 1
最优解:一步一步想明白
- 3于是变成 0-1 背包:dp[i][w]=用前 i 块、容量 w 内能凑到的最大重量。下面一张二维表逐格填。
- 4第 0 行:一块石头都还没加入,不管容量多大都只能凑出 0。这是递推的起点。
- 5加入石头 2,容量 0 比 2 还小,放不进,只能照搬上方 dp[0][0]=0。
- 6照搬上方,填入 0。
- 7加入石头 2,容量 1 比 2 还小,放不进,只能照搬上方 dp[0][1]=0。
- 8照搬上方,填入 0。
- 9加入石头 2,容量 2:不选它=上方 0;选它=腾出 2 后的 dp[0][0]=0 再加 2 = 2。
- 10选这块更划算,填入 2。
- 11加入石头 2,容量 3:不选它=上方 0;选它=腾出 2 后的 dp[0][1]=0 再加 2 = 2。
- 12选这块更划算,填入 2。
- 13加入石头 2,容量 4:不选它=上方 0;选它=腾出 2 后的 dp[0][2]=0 再加 2 = 2。
- 14选这块更划算,填入 2。
- 15加入石头 2,容量 5:不选它=上方 0;选它=腾出 2 后的 dp[0][3]=0 再加 2 = 2。
- 16选这块更划算,填入 2。
- 17加入石头 2,容量 6:不选它=上方 0;选它=腾出 2 后的 dp[0][4]=0 再加 2 = 2。
- 18选这块更划算,填入 2。
- 19加入石头 2,容量 7:不选它=上方 0;选它=腾出 2 后的 dp[0][5]=0 再加 2 = 2。
- 20选这块更划算,填入 2。
- 21加入石头 4,容量 0 比 4 还小,放不进,只能照搬上方 dp[1][0]=0。
- 22照搬上方,填入 0。
- 23加入石头 4,容量 1 比 4 还小,放不进,只能照搬上方 dp[1][1]=0。
- 24照搬上方,填入 0。
- 25加入石头 4,容量 2 比 4 还小,放不进,只能照搬上方 dp[1][2]=2。
- 26照搬上方,填入 2。
- 27加入石头 4,容量 3 比 4 还小,放不进,只能照搬上方 dp[1][3]=2。
- 28照搬上方,填入 2。
- 29加入石头 4,容量 4:不选它=上方 2;选它=腾出 4 后的 dp[1][0]=0 再加 4 = 4。
- 30选这块更划算,填入 4。
- 31加入石头 4,容量 5:不选它=上方 2;选它=腾出 4 后的 dp[1][1]=0 再加 4 = 4。
- 32选这块更划算,填入 4。
- 33加入石头 4,容量 6:不选它=上方 2;选它=腾出 4 后的 dp[1][2]=2 再加 4 = 6。
- 34选这块更划算,填入 6。
- 35加入石头 4,容量 7:不选它=上方 2;选它=腾出 4 后的 dp[1][3]=2 再加 4 = 6。
- 36选这块更划算,填入 6。
- 37加入石头 1,容量 0 比 1 还小,放不进,只能照搬上方 dp[2][0]=0。
- 38照搬上方,填入 0。
- 39加入石头 1,容量 1:不选它=上方 0;选它=腾出 1 后的 dp[2][0]=0 再加 1 = 1。
- 40选这块更划算,填入 1。
- 41加入石头 1,容量 2:不选它=上方 2;选它=腾出 1 后的 dp[2][1]=0 再加 1 = 1。
- 42不选更划算(或相等),填入 2。
- 43加入石头 1,容量 3:不选它=上方 2;选它=腾出 1 后的 dp[2][2]=2 再加 1 = 3。
- 44选这块更划算,填入 3。
- 45加入石头 1,容量 4:不选它=上方 4;选它=腾出 1 后的 dp[2][3]=2 再加 1 = 3。
- 46不选更划算(或相等),填入 4。
- 47加入石头 1,容量 5:不选它=上方 4;选它=腾出 1 后的 dp[2][4]=4 再加 1 = 5。
- 48选这块更划算,填入 5。
- 49加入石头 1,容量 6:不选它=上方 6;选它=腾出 1 后的 dp[2][5]=4 再加 1 = 5。
- 50不选更划算(或相等),填入 6。
- 51加入石头 1,容量 7:不选它=上方 6;选它=腾出 1 后的 dp[2][6]=6 再加 1 = 7。
- 52选这块更划算,填入 7。
- 53加入石头 5,容量 0 比 5 还小,放不进,只能照搬上方 dp[3][0]=0。
- 54照搬上方,填入 0。
- 55加入石头 5,容量 1 比 5 还小,放不进,只能照搬上方 dp[3][1]=1。
- 56照搬上方,填入 1。
- 57加入石头 5,容量 2 比 5 还小,放不进,只能照搬上方 dp[3][2]=2。
- 58照搬上方,填入 2。
- 59加入石头 5,容量 3 比 5 还小,放不进,只能照搬上方 dp[3][3]=3。
- 60照搬上方,填入 3。
- 61加入石头 5,容量 4 比 5 还小,放不进,只能照搬上方 dp[3][4]=4。
- 62照搬上方,填入 4。
- 63加入石头 5,容量 5:不选它=上方 5;选它=腾出 5 后的 dp[3][0]=0 再加 5 = 5。
- 64不选更划算(或相等),填入 5。
- 65加入石头 5,容量 6:不选它=上方 6;选它=腾出 5 后的 dp[3][1]=1 再加 5 = 6。
- 66不选更划算(或相等),填入 6。
- 67加入石头 5,容量 7:不选它=上方 7;选它=腾出 5 后的 dp[3][2]=2 再加 5 = 7。
- 68不选更划算(或相等),填入 7。
- 69加入石头 3,容量 0 比 3 还小,放不进,只能照搬上方 dp[4][0]=0。
- 70照搬上方,填入 0。
- 71加入石头 3,容量 1 比 3 还小,放不进,只能照搬上方 dp[4][1]=1。
- 72照搬上方,填入 1。
- 73加入石头 3,容量 2 比 3 还小,放不进,只能照搬上方 dp[4][2]=2。
- 74照搬上方,填入 2。
- 75加入石头 3,容量 3:不选它=上方 3;选它=腾出 3 后的 dp[4][0]=0 再加 3 = 3。
- 76不选更划算(或相等),填入 3。
- 77加入石头 3,容量 4:不选它=上方 4;选它=腾出 3 后的 dp[4][1]=1 再加 3 = 4。
- 78不选更划算(或相等),填入 4。
- 79加入石头 3,容量 5:不选它=上方 5;选它=腾出 3 后的 dp[4][2]=2 再加 3 = 5。
- 80不选更划算(或相等),填入 5。
- 81加入石头 3,容量 6:不选它=上方 6;选它=腾出 3 后的 dp[4][3]=3 再加 3 = 6。
- 82不选更划算(或相等),填入 6。
- 83加入石头 3,容量 7:不选它=上方 7;选它=腾出 3 后的 dp[4][4]=4 再加 3 = 7。
- 84不选更划算(或相等),填入 7。
- 85看最右下角 dp[5][7]=7:这堆最接近一半。差 = 15−2×7 = 1,就是最后剩下的最小重量。
⚠️ 容易写错的地方
✗ 错:当成「最大堆每次撞最重两块」贪心
✓ 对:贪心不对,要分两堆使差最小
局部撞最重不保证全局差最小
✗ 错:target 用 sum 而非 sum/2
✓ 对:容量只到 ⌊sum/2⌋
凑超过一半和凑不到一半对称,只需逼近一半
✗ 错:一维 dp 正序填容量
✓ 对:必须倒序 w 从大到小
正序会让一块石头被重复选(变完全背包)
完整代码(Python / C++ / Java)
Python
def lastStoneWeightII(stones):
s = sum(stones); t = s // 2
dp = [0] * (t + 1)
for x in stones:
for w in range(t, x - 1, -1):
dp[w] = max(dp[w], dp[w - x] + x)
return s - 2 * dp[t]C++
int lastStoneWeightII(vector<int>& stones){
int s = 0; for(int x : stones) s += x;
int t = s / 2;
vector<int> dp(t + 1, 0);
for(int x : stones)
for(int w = t; w >= x; --w)
dp[w] = max(dp[w], dp[w - x] + x);
return s - 2 * dp[t];
}Java
int lastStoneWeightII(int[] stones){
int s = 0; for(int x : stones) s += x;
int t = s / 2;
int[] dp = new int[t + 1];
for(int x : stones)
for(int w = t; w >= x; w--)
dp[w] = Math.max(dp[w], dp[w - x] + x);
return s - 2 * dp[t];
}复杂度
时间
O(n·sum)
n 块石头 × 容量 sum/2
空间
O(sum)
一维滚动数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最后一块石头 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
它和「分割等和子集」(LC416) 什么关系?+
同一个 0-1 背包骨架。416 问能否恰好凑出 sum/2(可行性);本题问最接近 sum/2 的最大值(最优化),把 dp 的布尔改成取 max 即可。
为什么相撞问题能等价成分两堆?+
每次相撞相当于给两块石头一正一负的符号,最终结果是所有石头带 ± 号求和的绝对值;要它最小,就是把石头分成符号相同的两组、使两组和尽量接近。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 最后一块石头 II 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。