416. Partition Equal Subset Sum
题目链接:416. Partition Equal Subset Sum
思路链接:代码随想录动态规划-分割等和子集
思路
转化为01背包问题需要一下元素:背包最大重量、物品重量、物品价值
这道题就是尽可能将数组分割成两个等和的数组,因此只要将数组sum除以2得到的值作为背包的最大重量就行了。至于物品负重和价值,这里最重要的点就是物品重量与价值看作是相等的!
- 背包负重(target):sum(nums) / 2
- 物品负重:sum[i]
- 物品价值:sum[i] 与负重相同
- 定义dp数组: 总容量是j的背包能获得的最大价值是dp[j]
- 确定递推关系式:dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]); 一维数组方法
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]); 二维数组方法 - 初始化dp数组:dp[0]一定为0,因为负重为0;之后的要取最小非负整数,因为不能覆盖住dp[j - nums[i]] + nums[i]
- 遍历顺序:对于一维数组来说,一定要先遍历物品,再倒序遍历背包。每一个元素一定是不可重复放入,所以从大到小遍历。可以通过画二维数组来理解这里为什么要倒序遍历。
- 打印dp数组
Code
class Solution {
public boolean canPartition(int[] nums) {
// 利用01背包问题的思路
// 背包负重(target):sum(nums) / 2
// 物品负重:sum[i]
// 物品价值:sum[i] 与负重相同
// 确定target
int sum = 0;
for (int i : nums) {
sum += i;
}
if (sum % 2 == 1) {
return false;
}
int target = sum / 2;
// 1. 定义dp数组: 总容量是j的背包能获得的最大价值是dp[j]
int[] dp = new int[target + 1];
// 2. 确定递推关系式
// dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]); 一维数组方法
// dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]); 二维数组方法
// 3. 初始化dp数组
// dp[0]一定为0,因为负重为0;之后的要取最小非负整数,因为不能覆盖住dp[j - nums[i]] + nums[i]
// 4. 遍历顺序
for (int i = 0; i < nums.length; i++) {
for (int j = target; j >= nums[i]; j--) {
// System.out.print("nums.length: " + nums.length + " i: " + i + " j: " + j);
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
// 5. 打印dp数组
// for (int i : dp) {
// System.out.print(i + " ");
// }
if (dp[target] == target) {
return true;
} else {
return false;
}
}
}
1049. Last Stone Weight II
题目链接:1049. Last Stone Weight II
思路链接:代码随想录动态规划-最后一块石头的重量 II
思路
把问题转化为01背包问题:为了得到剩下石头的最小重量,就只要尽可能将数组平均分成两组。注意,这里也是要将物品重量与物品价值相等。
- 背包最大负重 j: sum(stones) / 2
- 物品 i重量: stones[i]
- 物品 i价值: stones[i]
- 确定dp数组:最大载重为j的背包能获得的最大价值为dp[j]
- 递推公式:dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
- 初始化数组:dp[0]一定为0,后面因为不能覆盖其他值,也要为0
- 遍历方向:一维数组先遍历物品,再倒序遍历背包
- 打印数组
Code
class Solution {
public int lastStoneWeightII(int[] stones) {
// 思路:把数组分为尽可能平均的两组
// 背包最大负重 j: sum(stones) / 2
// 物品 i重量: stones[i]
// 物品 i价值: stones[i]
// 确定target负重
int sum = 0;
for (int i : stones) {
sum += i;
}
int target = sum / 2;
// 1. 确定dp数组
// 最大载重为j的背包能获得的最大价值为dp[j]
int[] dp = new int[target + 1];
// 2. 递推公式
// dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
// 3. 初始化数组
// dp[0]一定为0,后面因为不能覆盖其他值,也要为0
// 4. 遍历方向
for (int i = 0; i < stones.length; i++) {
for (int j = target; j >= stones[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
// 5. 打印dp数组
// for (int i : dp) {
// System.out.print(i + " ");
// }
return sum - dp[target] - dp[target];
}
}
494. Target Sum
题目链接:494. Target Sum
思路链接:代码随想录动态规划-目标和
思路
转化为01背包问题:还是需要分成两个部分:做加法的部分和做减法的部分。
做加法的总和为x,做减法的总和为sum - x,得到target = x - (sum - x),于是x = (target + sum) / 2
所以将问题转化为 装满重量为x的背包,最多有几种方法
- 确定dp数组:装满最大重量为j的背包,最多有dp[j]种方法 一维数组;将序号为[0, i]的物品nums[i]装满最大重量为j的背包,最多有dp[i][j]种方法 二维数组
- 确定递推公式:只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法;dp[j] += dp[j - nums[i]];
- 初始化数组:背包重量为0时,只有一种方法 dp[0] = 1, 其他为0, 因为都是要由dp[0]推导过来
- 遍历顺序:先遍历物品,在倒序遍历背包
- 打印数组
Code
class Solution {
public int findTargetSumWays(int[] nums, int target) {
// 做加法的总和为x
// 做减法的总和为sum - x
// 得到target = x - (sum - x)
// x = (target + sum) / 2
// -> 装满重量为x的背包,最多有几种方法
int sum = 0;
for (int i : nums) {
sum += i;
}
if (Math.abs(target) > sum ) {
return 0;
}
if ((target + sum) % 2 == 1) {
return 0;
}
int maxWeight = (target + sum) / 2;
// 1. 确定dp数组
// 装满最大重量为j的背包,最多有dp[j]种方法 一维数组
// 将序号为[0, i]的物品nums[i]装满最大重量为j的背包,最多有dp[i][j]种方法 二维数组
int[] dp = new int[maxWeight + 1];
// 2. 确定递推公式
// 只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法
// dp[j] += dp[j - nums[i]];
// 3. 初始化数组
// 背包重量为0时,只有一种方法 dp[0] = 1, 其他为0, 因为都是要由dp[0]推导过来
dp[0] = 1;
// 4. 遍历顺序
for (int i = 0; i < nums.length; i++) {
for (int j = maxWeight; j >= nums[i]; j--) {
// System.out.print(dp[j - nums[i]] + " ");
dp[j] += dp[j - nums[i]];
}
}
// 5. 打印数组
// for (int i : dp) {
// System.out.print(i + " ");
// }
return dp[maxWeight];
}
}
474. Ones and Zeroes
题目链接:474. Ones and Zeroes
思路链接:代码随想录动态规划-一和零
思路
还是转化成01背包问题,只不过现在的背包有两个维度了,因此要用二维数组表示。
现在的背包最大重量有两个维度:m个0, n个1
问题转化成利用下标为[0, i]的物品strs[i]装入背包,装满最大重量为m,n的背包最多需要几个物品
物品重量: x个0,y个1
- 定义dp数组:最多有i个0和j个1的strs的最大子集的大小为dp[i][j] (装满重量为i,j的背包最多需要dp[i][j]个物品)
- 确定递推公式:dp[i][j] = Math.max(dp[i][j], dp[i - x][j - y] + 1); 装入背包物品个数+1
- 初始化数组:背包重量为0时,显然是0个物品;为了防止初始化太大的正数,导致dp[i - x][j - y] + 1无法覆盖dp[i][j],后面的背包也都初始化为0
- 遍历顺序:先遍历物品,然后需要统计每个物品,也就是字符串中0和1的数量,然后倒序遍历背包
- 打印数组
Code
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
// 现在的背包最大重量有两个维度:m个0, n个1
// 问题转化成利用下标为[0, i]的物品strs[i]装入背包,装满最大重量为m,n的背包最多需要几个物品
// 物品重量: x个0,y个1
// 1. 定义dp数组
// 最多有i个0和j个1的strs的最大子集的大小为dp[i][j] (装满重量为i,j的背包最多需要dp[i][j]个物品)
int[][] dp = new int[m + 1][n + 1];
// 2. 确定递推公式
// dp[i][j] = Math.max(dp[i][j], dp[i - x][j - y] + 1); 装入背包物品个数+1
// 3. 初始化数组
// 背包重量为0时,显然是0个物品;为了防止初始化太大的正数,导致dp[i - x][j - y] + 1无法覆盖dp[i][j],后面的背包也都初始化为0
// 4. 遍历顺序
for (String str : strs) { // 首先遍历物品
int x = 0;
int y = 0;
for (int index = 0; index < str.length(); index++) { // 统计物品重量:0 和 1 的个数
if (str.charAt(index) == '0') {
x++;
} else {
y++;
}
}
for (int i = m; i >= x; i--) { // 倒序遍历背包
for (int j = n; j >= y; j--) {
dp[i][j] = Math.max(dp[i][j], dp[i - x][j - y] + 1);
}
}
}
// 5. 打印dp数组
// for (int i = 0; i < dp.length; i++) {
// for (int j = 0; j < dp[0].length; j++) {
// System.out.print(dp[i][j] + " ");
// }
// System.out.println("");
// }
return dp[m][n];
}
}