1. 关于动规
- 01背包思路
- 二维数组
- 一维数组
- 创建dp数组:j是目标总价,dp[j]可以是int[],也可能是boolean[]。例:int[]的dp:是在0~i个物品中,可以实现的里目标总价最近的价值
- 初始化:dp[j=0]?
- 状态转移:
- 注意遍历顺序:物品顺序遍历,然后背包容量倒序遍历,其遍历边界是到j-weight[i]为止
- 比较2种情况Math.max():从i+1个物品遍历到第i个物品时:
- 情况1:用得到物品i:dp[j-weight[i]] + val[i]
- 情况2:用不到物品i:仍然是(在物品数组i+1时搭配出来的)dp[j]不变
- 注意:如果返回int[],则这里的比较是指Math.max;如果返回boolean[],则这里的比较是指 v||
- 待施工
思路练习
- 怎么把思路转化成01背包?(谁是j,谁是dp[j]?谁是背包容量,谁是物品价值/重量?)
- 01背包的一维数组要注意什么?(j的遍历顺序?j的遍历边界?)
2. 例题
lc1049 最后一块石头重量II
前置知识点
int[]数组求和:Arrays.stream(nums).sum()
思路
转为01背包问题:
- 把石头分成尽可能等重的两堆,然后两堆相撞求得解
- 怎么样可以凑到尽可能=总重一半?用416分割等和子集的思想,即让i个石头中,凑到sum/2的可能性,只是这次不返回t/f,也不用追求完全=sum/2,而是要返回最佳情况下,凑到的最大石头总重
- 转嫁为背包思路
- 谁是背包?什么是背包容量?——sum/2即为背包容量
- 谁是j?——当背包容量为j时……这个j是滚动变化的,而不是静止的sum/2
- 谁是物品?——石头
- 谁是i?——石头们,0~i即为在前i个石头里,是否存在部分的石头可以使……
- 谁是物品重量?——每个石头的重量stones[i],即为在状态转移比较更新时的dp[j-stones[i]]+stones[i]中的第一个stones[i],此处代表weight[i]
- 谁是物品价值?——本题中,也是每个石头的重量stones[i],即为在状态转移比较更新时dp[j-stones[i]]+stones[i]中的第二个stones[i]
- 谁是背包?什么是背包容量?——sum/2即为背包容量
01背包的应用:
- 创建dp数组:故设定dp[j]表示,在背包容量(石头目标总重)为j时,能凑到的离背包容量最近的最大石头总重为dp[j]
- 状态转移:遍历到第i个石头时:
- 情况1:需要用到这i个石头 dp[j-stones[i]]+stones[i]
- 情况2:用不到这i个石头 dp[j]
- 比较2情况并更新:dp[j]=max(dp[j],dp[j-stones[i]]+stones[i])
- 遍历顺序:一维数组,先顺序遍历物品(石头)i,再倒序遍历背包容量j
- 初始化:若j=0,则答案为0,跟默认的是一样的,相当于不用初始化
易错点
注意j遍历的边界,为了步数组越界,需要的是j-stones[i]>=0,即j>=stones[i]
代码实现
class Solution {
public int lastStoneWeightII(int[] stones) {
//计算目标重量:总重的一半
int sum = Arrays.stream(stones).sum();
int targetsum=sum/2;
//建立dp数组,这里已经默认dp[?]=0了
int[] dp=new int[targetsum+1];
//初始化:本题没有
//状态转移(注意一维数组的遍历顺序
//易错,注意j遍历的边界,为了步数组越界,需要的是j-stones[i]>=0,即j>=stones[i]
for(int i=0;i<stones.length;i++){
for(int j=targetsum;j>=stones[i];j--){
dp[j]=Math.max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
//最终撞击两堆石头,即sum-2dp[targetsum]
return sum-2*dp[targetsum];
}
}