1049. 最后一块石头的重量 II
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
*
如果 x == y,那么两块石头都会被完全粉碎;
*
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。
示例:
输入:[2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
提示:
1.
1 <= stones.length <= 30
2.
1 <= stones[i] <= 1000
思路1.0:每次优先拿两个最大石头来砸
但每次都要找到两个最大值消耗很高
思路2.0(看了题解):每次拿两个石头对砸,砸到最后其实就是两堆石头对砸,且使得两堆石头质量和的差的绝对值最小。
最理想的情况是质量分别为x,y的两堆石头与总质量成:xysum/2,x+y==sum
其他情况则是:x<sum/2<y或者y<sum/2<x
要使得abs(x-y)最小,则在x与y质量不等时,较小的那一份尽量大,所以这题可以转变为:从stones中取若干石头,使得其质量和在sum/2的限制下达到最大(01背包问题)。
设置vector dp(sum/2)
状态转移公式为:dp[n]=max(dp[n],dp[n-stone_wt]+stone_wt);
代码1.0
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum = 0;
for (const auto& i : stones)
{
sum += i;
}
int limit = sum / 2;
vector<int> dp(limit+1);
for (const auto& stone : stones)
{
for (int j = limit; j >= stone; j--)
{
dp[j] = max(dp[j], dp[j - stone] + stone);
}
}
return (sum - dp[limit]) - dp[limit];
}
};
在看题解和做题的过程中,一直对“01背包”问题的“滚动数组解法”不是特别理解,494. 目标和也是如此,在此要对其定点爆破了。
“01背包与滚动数组”
示例代码:
1. const int N = 3;//物品个数
2. const int V = 5;//背包最大容量
3. int weight[N + 1] = {0,3,2,2};//物品重量
4. int value[N + 1] = {0,5,10,20};//物品价值
1. for (int i = 1;i <= N;i++) //枚举物品
2. {
3. for (int j = V;j >= weight[i];j--) //枚举背包容量,防越界,j下限为 weight[i]
4. {
5. f[j] = Max(f[j],f[j - weight[i]] + value[i]);
6. }
7. }
8. return f[V];
观察一维状态转移方程:
f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])
首先我们明确三个问题
1) v - weight[i] < v
2) 状态f[i][v] 是由 f[i - 1][v] 和 f[i - 1][v - weight[i]] 两个状态决定
3) 对于物品i,我们在枚举背包容量时,只要背包容量能装下物品i 且 收益比原来的大,就会成功放入物品i。
由此得出的几个要点:
j逆序遍历的原因:为了保证在最大的那个最优解在需要用到他对应的较小最优解时(例如i=2,j=5时的f[5],即f[2][5]=f[1][5-weight[2]]+value[2],
此时f[2][5]也就是f[5]需要f[i-1][3]=f[1][3]即 在i=1,j=3时f[3]的值)
,那个较小最优解不能被覆盖,还保留着f[i-1][j]的值。
j的值取在[weight[i],V]的原因,因为[0,weight[i]),是因为容量太小,而前i个物品肯定放不进去的情况,所以省去这些无用功。
下次再忘了就拿这个例子手动debug一次,配合这条笔记食用,就明白了