1049. 最后一块石头的重量 II
思路:本题的目的是将石头尽量的分成重量相同的两堆,求相撞之后剩下的石头最小重量。这样就可以看成01背包问题。与昨天的分割等和子集十分相似。
动态规划五步曲:
- dp[j] 表示容量为j的背包,最多可以背容量为dp[j]的石头。
- 递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])
- 初始化,01背包分割子集问题,dp数组的长度为物品数量的一半。dp[0] = 0,其他非0下标处也为0。
- 遍历顺序,先遍历物品,再从后到前遍历背包(防止重复放入)
- 举例说明
class Solution {
public int lastStoneWeightII(int[] stones) {
// dp[j] 表示容量为j的背包,最多能否放下价值为dp[j]的石头
int sum = 0;
for (int stone : stones) {
sum += stone;
}
int len = sum / 2;
int[] dp = new int[len + 1];
for (int i = 0; i < stones.length; i++) {
for (int j = len; j >= stones[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
return sum - 2 * dp[len];
}
}
494. 目标和
思路:假设加法的总和为x,那么减法的总和就是sum - x。而我们要求的是x - (sum - x) = target,即x = (target + sum) / 2,此时class Solution {
public int findTargetSumWays(int[] nums, int target) {
// dp[j] 表示填满容量为j的背包,共有dp[j]种方法
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((target + sum) % 2 != 0) return 0;
if (Math.abs(target) > sum) return 0;
// 背包的容量为nums里面需要相加的总值
int[] dp = new int[(target + sum) / 2 + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = dp.length - 1; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[dp.length - 1];
}
},问题就转化为装满容量为x的背包有多少种方式。本题中我们还要注意两种情况,如果target+sum不能够背2整数,题目无解,如果target的绝对值大于sum,题目无解。
本题就变成了01背包求组合问题,动态规划五步曲:
- dp[j] 表示装满容量为j的背包,有dp[j]种方法。
- 所有求组合类问题的递推公式都是类似于这种:
dp[j] += dp[j - nums[i]]
- 初始化dp[0] = 1
- 先遍历物品再倒序遍历背包容量
- 举例说明
class Solution {
public int findTargetSumWays(int[] nums, int target) {
// dp[j] 表示填满容量为j的背包,共有dp[j]种方法
int sum = 0;
for (int num : nums) {
sum += num;
}
if ((target + sum) % 2 != 0) return 0;
if (Math.abs(target) > sum) return 0;
// 背包的容量为nums里面需要相加的总值
int[] dp = new int[(target + sum) / 2 + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = dp.length - 1; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[dp.length - 1];
}
}
本题也是典型的回溯法,可以用回溯法进行暴搜,但是好像会超时。
474.一和零
思路:本题中strs 数组里的元素就是物品,每个物品都是一个!
而m 和 n相当于是一个背包,两个维度的背包。
本题依然是01背包问题,但是背包有两个维度,就是存放0的个数和存放1的个数。
动态规划五步曲:
- dp[i][j] 代表最多由i个0和j个1的最大子集大小为dp[i][j]
- 可以由两个方向退出dp[i][j],一个是放入strs[i],一个是不放strs[i],
所以递推公式:dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
- 初始化dp[0][0] = 0
- 先遍历物品,然后从后向前遍历背包的容量。
- 举例说明
class Solution {
public int findMaxForm(String[] strs, int m, int n) { // 含有两个维度的01背包问题
// dp[i][j] 代表最多由i个0和j个1的最大子集大小为dp[i][j]
int[][] dp = new int[m + 1][n + 1];
for (String str : strs) {
int zeroNum = 0, oneNum = 0;
char[] chs = str.toCharArray();
for (char ch : chs) {
if (ch == '0') {
zeroNum++;
} else {
oneNum++;
}
}
for (int i = dp.length - 1; i >= zeroNum; i--) {
for (int j = dp[i].length - 1; j >= oneNum; j--) {
dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
return dp[dp.length - 1][dp[0].length - 1];
}
}