题目:
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。
来源:力扣(LeetCode)
思路:模仿416.分割等和子集,主要是背公式 + 先脑筋急转弯理解题目想要的思路
这道题如果不是提示,几乎想不到使用DP,是01背包问题;先想到的是回溯或者递归等
1)不管是怎么取石子,最后的结果都是相互抵消的问题;
如果没有完全抵消,肯定会不停的剩下石子;就一直抵消下去
抵消了就是0,不抵消最后只能剩一个!
2)题目给出了,正整数!所以如果总和为 偶数,最后就全部抵消!
如果不为偶数,最大可抵消的就是 sum / 2!
将该值作为背包容量,去取石子;看最大能取到的石子重量,就是能抵消的值!
一个疑惑:打碎的石子怎么计算呢!该取法是取原来的整块,但是会出现更新更小的石子!
3)一维dp中:背包容量需要倒序遍历;为了使物品只入队一次,只有一个物品
从前往后,会出现dp[j - weight[i]]已经入队了某物品,此时又将某物品入队!因为是两层for
物品放外层,为了防止:不同情况下,物品只能入队一次
★★:dp[i][j] 只和 dp[i - 1][j] 和dp[i - 1][j - weight[i]] 有关;可以看出,只和上一行的内容有关;
因此可以使用滚动数组,实现空间的优化![j - weight[i]] 只影响了列!
滚动数组:
背包问题的理解,来自代码随想录:
物品按顺序添加,背包按倒序遍历;
几个物品就尝试几次进入背包的操作;可以理解为五个不同容量的背包;
由于每次物品进入背包会涉及前面的内容,如果背包由小到大遍历,同一个物品会多次进入背包!
容易困惑的点:还是背包中物品价值的更新问题,二维容易理解;不要钻牛角尖,其实很好捋顺!
代码:
1)二维数组
class Solution {
public int lastStoneWeightII(int[] stones) {
//这道题,第一想法,看着像是回溯或者递归这种的!
//如果不是按照攻略刷题很难想到是 01背包
int n = stones.length;
if(n == 1) return stones[0];
//先计算总和
int sum = 0;
for(int i = 0;i < n;i++){
sum += stones[i];
}
//类似上一道,分割等和子集;最大只能取到 一般,也就是最大只能抵消一半,此时就是剩下0
int res = sum;
sum = sum / 2;
int[][] dp = new int[n + 1][sum + 1];
for(int i = 0;i <= n;i++){
dp[i][0] = 0;//初始化
}
//二维dp赋值
for(int i = 1;i <= n;i++){
for(int j = 1;j <= sum;j++){
//对照dp数组,dp的i行其实就是原数组的 i - 1行
if(j < stones[i - 1]) dp[i][j] = dp[i - 1][j];
else
dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - stones[i - 1]] + stones[i - 1]);
}
}
//最后dp[n][sum]就是最大可消去的石头重量
return res - 2 * dp[n][sum];
}
}
2)一维dp数组
class Solution {
public int lastStoneWeightII(int[] stones) {
//这道题,第一想法,看着像是回溯或者递归这种的!
//如果不是按照攻略刷题很难想到是 01背包
int n = stones.length;
if(n == 1) return stones[0];
//先计算总和
int sum = 0;
for(int i = 0;i < n;i++){
sum += stones[i];
}
//类似上一道,分割等和子集;最大只能取到 一般,也就是最大只能抵消一半,此时就是剩下0
int res = sum;
sum = sum / 2;
int[] dp = new int[sum + 1];
//一维dp赋值
for(int i = 0;i < n;i++){
//背包容量需要倒序遍历;为了使物品只入队一次,只有一个物品
//从前往后,会出现dp[j - weight[i]]已经入队了某物品,此时又将某物品入队!因为是两层for
//物品放外层,为了防止:不同情况下,物品只能入队一次
for(int j = sum;j >= stones[i];j--){
dp[j] = Math.max(dp[j],dp[j - stones[i]] + stones[i]);
}
}
//最后dp[n][sum]就是最大可消去的石头重量
return res - 2 * dp[sum];
}
}