[M背包] lc1049. 最后一块石头的重量 II(01背包+知识理解+好题+思维)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[M背包] lc1049. 最后一块石头的重量 II(01背包+知识理解+好题+思维)相关的知识,希望对你有一定的参考价值。
1. 题目来源
2. 题目解析
好难的一道 01 背包变种题…一开始还以为是选相邻石头,误判为 区间 dp
…
题目描述中有点误导人的意思,不需要限定 x<=y
才做后两种操作,直接可以任选两个石头 x
、y
,合并后新石头重量为 abs(x-y)
即可,当新石头质量为 0 时,则不再进入石头堆了。
那么抽象的来想一下,每次合并过程其实就是针对两个石头选择正负号的过程,一直到最后的合并,也是两个石头选择正负号的过程。那么此时,我们按照符号统一将正数、负数分类看作两堆,问题就转化成了,只要两堆石子的数值越接近,那么合并后石头越小。
看下例:
输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],我将其改为 [(4-2), 7, 1, 8, 1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],我将其改为 [(4-2), (8-7), 1, 1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],我将其改为 [(4-2)-1, (8-7), 1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值, [(4-2)-1, (8-7)-1],
组合 1 和 0,得到 1,所以数组转化为 [1],这就是最优值 [((4-2)-1-((8-7)-1)]
添加了一步,方便理解。我们对这最后一步做化简 4-2-1-8+7+1=1 ,再以正负号分类:4、7、1 为一类、-2 -1 -8 为一类。
经上分析,问题等价于将一堆数划分成两组,并保证两组的差值最小。
那么就变成了每个数选且只选一次,用一个容量为 sum/2
的背包,尽可能的装,看最多能装多少石头。
这里是要分成两组数,求两组数的差值尽量小,那么两组数肯定是要往 sum/2
里靠,小的一方越接近越好,将其假设为 a
,大的一方就是 sum-a,则差值即为 sum-a-a
,即为最优解。
综上所述,本题就是个 01 背包,容量上限为 sum/2
,求尽可能填满背包的重量是多少。
总结来看,选不选问题且直选一次,转化为 01 背包后,可以自定义背包容量,就能够快速选择出不超过背包容量的合法装填方案。
关于 01 背包初始化,以及 01 背包循环内部怎么写…
- 第二层循环背包容量是从 0 开始的,不要从 1 开始
- 且不选择该物品一定可以不被选择,那么针对
f[i][j] = f[i-1][j]
一定是需要被执行的,所以不要写if(j>=stones[i-1]) .... else f[i][j]=f[i-1][j]
,这样会导致f[i][j]=f[i-1][j]
有些部分不会被执行,导致状态转移错误! - 在
vector<vector<bool>>
中,写的是if---else
结构,但是在此的if
判断里面把else
里面的语句给包含了,所以这个else
里面的语句不论怎样都会执行…状态转移永远正确。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n 2 ) O(n^2) O(n2)
注意:
在这一开始将 j
手误写成了从 1 开始,出错了。等于说除过 f[0][0]=true
,其余的 f[i][0]=false
因为没办法进行状态转移,所以需要在初始化的时候,将 f[i][0]=true
进行全部初始化,这样 j
从 1 开始也是可以的。
实际上在第一个 for
循环后面紧接着一个 f[i][0]=true
也是可以的,因为毕竟一个不选也是合法的。
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int n = stones.size(), sum = 0;
for (int x : stones) sum += x;
int m = sum / 2;
vector<vector<bool>> f(n + 1, vector<bool>(m + 1));
// f[i][j] 表示从前i个数中选,能否在背包下装质量恰好为j。
f[0][0] = true;
for (int i = 1; i <= n; i ++ )
for (int j = 0; j <= m; j ++ ) { // 不要把 j 写成从 1 开始...
if (j >= stones[i - 1]) f[i][j] = f[i - 1][j] || f[i - 1][j - stones[i - 1]];
else f[i][j] = f[i][j] || f[i - 1][j];
// 另一种写法
// f[i][j] = f[i][j] || f[i - 1][j];
// if (j >= stones[i - 1]) f[i][j] = f[i - 1][j] || f[i - 1][j - stones[i - 1]];
}
for (int j = m; ~j; j -- )
if (f[n][j])
return sum - j - j;
return sum;
}
};
经典的 01 背包一维优化:
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int n = stones.size(), sum = 0;
for (int x : stones) sum += x;
int m = sum / 2;
vector<bool> f(m + 1);
f[0] = true;
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= stones[i - 1]; j -- )
f[j] = f[j] || f[j - stones[i - 1]];
for (int j = m; ~j; j -- )
if (f[j])
return sum - j - j;
return sum;
}
};
直接背包存质量:
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int n = stones.size(), sum = 0;
for (int x : stones) sum += x;
int m = sum / 2;
vector<vector<int>> f(n + 1, vector<int>(m + 1));
f[0][0] = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 0; j <= m; j ++ ) {
f[i][j] = f[i - 1][j];
if (j >= stones[i - 1]) f[i][j] = max(f[i][j], f[i - 1][j - stones[i - 1]] + stones[i - 1]);
}
int res = 1e9;
for (int i = m; ~i; i -- ) res = min(res, sum - f[n][i] - f[n][i]);
return res;
}
};
以上是关于[M背包] lc1049. 最后一块石头的重量 II(01背包+知识理解+好题+思维)的主要内容,如果未能解决你的问题,请参考以下文章
动态规划第六篇:01背包问题(分割等和子集 + 最后一块石头的重量 II)
动态规划第六篇:01背包问题(分割等和子集 + 最后一块石头的重量 II)