本次题目
- 416 分割等和子集
- 1049 最后一块石头的重量II
- 494 目标和
- 474 一和零
01背包
- 01背包:容量为w的背包,n个物品,第i件物品的重量为weight[i],价值为value[i],每个物品只能装一次,求装哪些物品总价值最大。
- 二维dp数组01背包:
- 定义dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少;
- 递推公式当背包还能装下第i件物品时,dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
- 初始化dp[i][0] = 0,当背包容量j小于第0件物品重量weight[0]时,dp[0][j] = 0,否则dp[0][j] = value[0];
- 遍历顺序由小到大,这里先遍历物品再遍历背包;
- 举例。
- 一维滚动数组01背包:
- 定义一维数组d[j]表示上一层(i - 1)拷贝到当前层(i),即到物品i时放进容量为j的背包的最大总价值;
- 递推公式dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
- 物品由小到大,背包由大到小(由于上层覆盖当前层,防止同一物品被放多次);
- 遍历顺序只能先物品再背包;
- 举例。建议使用一维滚动数组。
416 分割等和子集
- 01背包DP:
- 定义dp[j]表示容量为j的背包,放入的数之和最大为dp[j],长度为目标和加1;
- 递推公式dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
- 初始化为全0(比物品价值小);
- 遍历顺序先遍历物品(由小到大)再遍历背包(由大到小);
- 举例。
class Solution {
public boolean canPartition(int[] nums) {
int leng = nums.length;
if(leng == 1) return false;
int sum = 0;
for(int i = 0; i < leng; i++){
sum += nums[i];
}
if(sum % 2 == 1) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
for(int i = 0; i < leng; i++){
for(int j = target; j >= nums[i]; j--){
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}
1049 最后一块石头的重量II
- 同上416 分割等和子集,将物品换位石头,还是将石头分为重量相同的两堆。
- 定义dp[j]表示容量为j的背包最多可以背重量为dp[j]的石头;
- 递推公式dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
- 将dp[0]初始化为0,长度为最大重量的一半;
- 遍历顺序先遍历物品(由小到大)再遍历背包(由大到小);
- 举例。
- 注意:取目标重量时向下取整,所以dp数组得到的重量较小。
class Solution {
public int lastStoneWeightII(int[] stones) {
int leng = stones.length;
if(leng == 1) return stones[0];
int sum = 0;
for(int i = 0; i < leng; i++){
sum += stones[i];
}
int target = sum / 2;
int[] dp = new int[target + 1];
for(int i = 0; i < leng; i++){
for(int j = target; j >= stones[i]; j--){
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
return sum - 2 * dp[target];
}
}
494 目标和
- 01背包DP(组合):
- 定义dp数组表示容量为j的背包装加法装满有dp[j]种方法;
- 递推公式dp[j] += dp[j - nums[i]];
- 初始化dp[0] = 1,长度为目标值加数组之和除以2(加1);
- 遍历顺序先物品(由小到大)再背包(由大到小);
- 举例。
- 注意:当目标数与数组之和不能被2整除时无结果。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for(int i = 0; i < nums.length; i++) sum += nums[i];
if((sum + target) % 2 != 0) return 0;
int t = (sum + target) / 2;
if(t < 0) t = -t;
int[] dp = new int[t + 1];
dp[0] = 1;
for(int i = 0; i < nums.length; i++){
for(int j = t; j >= nums[i]; j--){
dp[j] += dp[j - nums[i]];
}
}
return dp[t];
}
}
474 一和零
- 01背包DP:
- 定义dp[i][j]表示:最多有i个0和j个1的strs的最大子集的大小为dp[i][j];
- 递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
- 初始化dp[0] = 0;
- 遍历顺序先物品(由小到大)再背包(二维,由大到小);
- 举例。
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m + 1][n + 1];
for(String str : strs){
int zeroNum = 0;
int oneNum = 0;
char[] s_arr = str.toCharArray();
for(char s : s_arr){
if(s == '1'){
oneNum += 1;
}else{
zeroNum += 1;
}
}
for(int i = m; i >= zeroNum; i--){
for(int j = n; j >= oneNum; j--){
dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
return dp[m][n];
}
}