思路详解
例子[2,7,4,1,8,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 - ( ( 4 -2 ) - ( 8 - 7 ) ) ==> ( 2 + 8 +1 ) - ( 4 + 7 )
1)对于我们来说如果数组只有两个元素,这是最舒服的,因为此时答案就是大 - 小。从上面例子得到的最后等式,显然这道题是希望我们从数组中选出两堆数,使他们的和的差值最小。
2)
3)假设这堆数位集合A,那么对于每个元素来说,都可以放进集合A或者不放进集合A,这不就跟01背包很相似了嘛?我们用一个 dp[i][j] 来表示前 i 个数,集合A的大小为 j 的时候,能达到的最大和。
(这里有点绕口,如果觉得我没有表述清晰可以debug一下!!)。那么对于每个元素:
如果不选择该元素(即该元素不放进集合A中),此时问题转换为"前 i -1 个数,集合A的大小为 j 的时候,能达到的最大和";
如果选择该元素,那么此时问题转换为"前 i - 1 个数,集合A的大小为 j - stones[i] 时,能达到的最大和"。
public int lastStoneWeightII(int[] stones) {
if(stones==null || stones.length==0)
return 0;
int sum = 0;
for(int stone:stones)
sum += stone;
int target = sum/2 + 1;
int len = stones.length;
//dp[i][j]:前i个数字,背包容量为j的最大价值
int[][] dp = new int[len+1][target];
for(int i=1;i<=len;i++){
for(int j=1;j<target;j++){
dp[i][j] = dp[i-1][j];
//当第i个元素小于背包容量的时才可以考虑加入背包中
//第i个元素如果不放进背包中,则dp[i][j] = dp[i-1][j]
//如果放进背包中,则dp[i][j]=dp[i-1][j-stones[i-1]]+stones[i-1]
if(stones[i-1] <= j)
dp[i][j] = Math.max(dp[i][j],dp[i-1][j-stones[i-1]]+stones[i-1]);
}
}
//for(int[] arr:dp)
//System.out.println(Arrays.toString(arr));
return sum-2*dp[len][target-1];
}
大家如果还有点小疑惑就一定要动手填表格!!这次填完印象就会更加深刻了!!
优化
我们仔细观察一下状态转移方程:dp[i][j] = Math.max(dp[i][j],dp[i-1][j-stones[i-1]]+stones[i-1]);
可以发现,每一次都只参照了上一行中的dp,因此我们可以把空间复杂度从二维降到一维。
public int lastStoneWeightII2(int[] stones){
if(stones==null || stones.length==0)
return 0;
int sum = 0;
for(int stone:stones)
sum += stone;
int target = sum/2+1;
int len = stones.length;
int[] dp = new int[target];
for(int i=0;i<len;i++){
//如果背包j的容量小于元素i 那么可以提前退出循环
for(int j=target-1;j>=stones[i];j--){
//dp[j]等价于dp[i-1][j]
//dp[j-stones[i]]+stones[i]等价于dp[i-1][j-stones[i]]+stones[i]
dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i]);
}
//System.out.println(Arrays.toString(dp));
}
return sum-2*dp[target-1];
}
如果博主表述的有哪些不清晰欢迎大家在评论区指出哦!如果有帮助的话还请点赞!!谢谢大家