1049. 最后一块石头的重量 II
实际上还是01背包问题。
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = Arrays.stream(stones).sum();
int target = sum/2;
int[] dp = new int[target + 1];
for(int i=0; i<stones.length; i++){
for(int k=target; k>=stones[i]; k--){
dp[k] = Math.max(dp[k], dp[k-stones[i]] + stones[i]);
}
}
return Math.abs(sum - dp[target] * 2);
}
}
494. 目标和
其实还是01背包问题,但是却有一点不一样:
- 相同点:将所有的数分为——正数组合、负数组合。两者之和为target。
- 不同点:不再是求背包最多能装多少了,而是最多装多少的情况下,有多少种装法——变成了组合问题。
不同点:
- 和:
(sum+target)/2
,将target算作正数集合里的一员(甭管它是正是负)。也正因此,可以理解为,正数会被装进背包,而负数不会。这样就避免了要把所有东西都装进背包(这样不是01背包问题了)。 - dp含义:切合题目的含义——有多少种装法。
dp[i][j]
:使用 下标为[0, i]
的nums[i]
能够凑满j
(包括j
)这么大容量的包,有dp[i][j]
种方法。
这里是正好装满该空间,而不是一定要达到最后条件。一开始想岔劈了。。 - 终止条件:有两个,详见开头的两个
if
判断。 - 初始条件:不再全为0了,因为容量为0时,也算是装满了包。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = Arrays.stream(nums).sum();
if((sum+target)%2==1) return 0;
if(Math.abs(target)>sum) return 0;
int size = (sum+target)/2;
int[] dp = new int[size+1];
// 初始条件:对于没有物品可选的时候,装满容量为0的背包,有1种方法。但是装不满其他容量的背包,其他为0。
dp[0] = 1;
for(int i=0; i<nums.length; i++){
for(int k=size; k>=nums[i]; k--){
dp[k] += dp[k-nums[i]]; // 装满该容量,有多少种方法——两种情况:不加该物品(dp[k]);加入该物品(dp[k-nums[i]])
}
}
return dp[size];
}
}
474.一和零
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
// 首先从参数中,区分物品与容量。
// strs:物品,需要一个个考虑。
// m,n:限制条件,也就是容量。
int[][] dp = new int[m+1][n+1]; // 容量+1是考虑到容量为0的情况。
// 初始化:初始值为0
// 对于每个物品,遍历的每个容量
for(int i=0; i<strs.length; i++){
// 计算
int count0 = 0;
int count1 = 0;
for(int k = 0; k < strs[i].length(); k++){
if(strs[i].charAt(k)=='1') count1++;
else if(strs[i].charAt(k)=='0') count0++;
}
// 递推
for(int i0=m; i0>=count0; i0--){ // 限制条件的剪枝:只有装下该物品、不装该物品两种情况
for(int i1=n; i1>=count1; i1--){
dp[i0][i1] = Math.max(dp[i0][i1], dp[i0-count0][i1-count1] + 1);
}
}
}
return dp[m][n];
}
}
小小总结一下背包的公式
- dp含义:
new int[maxSize+1]
对于每个容量都考虑到了,包括容量为0。
同时也避免了下标与容量不匹配的情况,免得加一减一。 - 遍历:首先分别考虑一个个加入物品,然后再考虑加入物品之后的容量——这样很容易递推,只有两种情况,放入该物品/不放。
因此,外循环是物品循环,内循环是容量循环(而且还是倒着遍历)。 - 剪枝:条件是,此时容量能否装下该物品,否则不变。
正因此,同时也避免了内部的条件判断。 - 递推公式(滚动数组):就是将放入/不放入两种情况考虑。要么是
dp[k]
,维持上一轮的结果;要么是dp[k-cost[i]] + value[i]
,新的结果。
还是要注意滚动数组问题的思路:物品数量与容量都是不断增加的,由此才会滚动。
物品数量:就是将已知的元素,一个一个放入集合,然后考虑整个集合的问题。
容量:就是限制条件。本来限制条件很大,但是先将其缩小,一步一步计算。所以,dp的长度,与这个限制条件的大小有关。
可以将“474.一和零 ”作为示例。