代码随想录训练营 Day43
今日任务
1049.最后一块石头的重量Ⅱ
494.目标和
474.一和零
语言:Java
1049. 最后一块石头的重量Ⅱ
链接:https://leetcode.cn/problems/last-stone-weight-ii/
问题是如何转化为背包问题的?参考视频
以最简单的3个物品为例,x1,x2和x3,其中x1>x2,x3>x1-x2,最后得到的结果就是x3-(x1-x2)=x3-x1+x2,也就是说题目相当于将所有数据分为两组,再对每组分别求和得到S1和S2,要我们返回S1和S2的最小差值,其中S2=sum-S1,S2-S1=sum-2×S1,为了让这个差值尽可能小,我们就要保证S1尽可能大,也就是让S1尽可能接近sum的一半。
class Solution {
public int lastStoneWeightII(int[] stones) {
//1.明确dp数组及下标含义: dp[j]代表容量为j的背包最大重量为dp[j]
//2.递归公式: dp[j] = max(dp[j], dp[j-weight[i]] + value[i])
//3.初始化: 0
//4.遍历顺序: 外层循环物品,内层循环背包(从后向前循环)
//5.打印dp数组
//问题转化:将所有的石头分成重量尽可能相近的两堆
int sum = 0;
for(int i = 0; i < stones.length; i++){
sum += stones[i];
}
int target = sum / 2;
int[] dp = new int[1501];
//weight[i]=value[i]=stones[i]
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]);
}
}
//return Math.abs((sum - dp[target]) - dp[target]);
//因为sum/2是向下取整,所以sum-dp[target]一定大于dp[target]
return (sum - dp[target]) - dp[target];
}
}
494. 目标和
链接:https://leetcode.cn/problems/target-sum/
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int count = 0;
//error: 容量为j的背包最大价值为dp[j]
//填满容量为j的背包有dp[j]种方法
//分成两组,两组和分别为S1和S2
//最终S1和S2的差值为target
//让S1和S2相等,都等于(sum-target)/2
int sum = 0;
for(int i = 0; i < nums.length; i++){
sum += nums[i];
}
if(Math.abs(target) > sum) return 0; //target可能为负
if((sum - target) % 2 == 1) return 0; //两边和肯定不同,无法凑出式子
//S1-S2=target
//sum-S1*2=target
int goal = (sum - target) / 2;
int[] dp = new int[goal + 1];
dp[0] = 1; //填满容量为0的背包有1种方法
for(int i = 0; i < nums.length; i++){
for(int j = goal; j >= nums[i]; j--){
//dp[j]: 不取当前物品有dp[j]种取法
//dp[j-nums[i]]: 取当前物品有dp[j-nums[i]]种取法
//将两个取法相加就是当前最多种取法
dp[j] += dp[j - nums[i]]; //这里是求有多少种方法,所以不需要取max,直接相加即可
}
}
return dp[goal];
}
}
474. 一和零
链接:https://leetcode.cn/problems/ones-and-zeroes/
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
//本题使用二维dp数组
//使用二维数组是因为物品的重量有两个维度
//解法相当于01背包问题滚动数组的解法(遍历顺序)
//本质还是在原来的基础上不断更新同一个dp数组,所以和一维滚动数组是相同的,要防止数据覆盖的问题
//如果不希望数据覆盖,要使用三维数组
//dp[i][j]代表最多i个0和j个1的最大子集长度为dp[i][j]
int[][] dp = new int[m + 1][n + 1];
//strs[i]的最小长度为1,所以对于最多0个0和0个1的情况,取法为0
for(String str: strs){
int zeroNum = 0;
int oneNum = 0;
for(int i = 0; i < str.length(); i++){
if(str.charAt(i) == '0') zeroNum++;
else oneNum++;
}
//相当于二维数组从右下角开始倒序遍历
for(int i = m; i >= zeroNum; i--){
for(int j = n; j >= oneNum; j--){
//dp[i][j]: 不取当前的字符串,子集长度不变
//dp[i-zeroNum][j-oneNum]+1: 取当前字符串,子集长度要加1
dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
return dp[m][n];
}
}