⚡刷题计划day30 动规01背包(二)继续,可以点个免费的赞哦~
目录
题目一:494.目标和
494. 目标和
(https://leetcode.cn/problems/target-sum/description/)
此题可以使用回溯,但容易超时
本题将使用动规,背包的方法来解决这个问题。
假设加法的总和为x,那么减法对应的总和就是sum - x。
所以我们要求的是 x - (sum - x) = target
x = (target + sum) / 2
此时问题就转化为,用nums装满容量为x的背包,有几种方法。
这里的x,就是bagSize,也就是我们后面要求的背包容量。
(target + sum) / 2
应该担心计算的过程中向下取整有没有影响,其实就是无解的。
同时如果target 的绝对值已经大于sum,那么也是没有方案的。
if(sum<Math.abs(target) || (sum+target)%2==1){
return 0;
}
五部曲:
1.确定dp数组以及下标的含义
使用 二维 dp数组求解本题,dp [i][j]
:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]
种方法。
2.确定递推公式
过程抽象化如下:
-
不放物品i:即背包容量为j,里面不放物品i,装满有
dp[i - 1][j]
中方法。 -
放物品i: 即:先空出物品i的容量,背包容量为(j - 物品i容量),放满背包有
dp[i - 1][j - 物品i容量]
种方法。
本题中,物品i的容量是nums[i],价值也是nums[i]。
递推公式:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]
;
3.dp数组如何初始化
先明确递推的方向,如图,求解dp[2][2]
是由 上方和左上方推出。
上方:不放物品i。
左上方:放物品i,先把i的空间留出来,然后截止上一个物品dp[i - 1][j - nums[i]
有多少种,dp[i][j]
就有多少种。
那么二维数组的最上行 和 最左列一定要初始化,这是递推公式推导的基础,如图红色部分:
关于dp[0][0]
的值,即 放0件物品,方法数量是1。
初始化最上行dp[0][j]
,
dp[0][j]
:只放物品0, 把容量为j的背包填满有几种方法。
只有背包容量正好为 物品0 的容量的时候,才能装满,方法数为1,
其他情况下,装不满或装不下。
所以初始化:dp[0][nums[0]]= 1
,其他均为0 .
初始化最左列
dp[i][0]
: 背包容量为0, 放物品0 到 物品i,装满有几种方法。
正常我们应该便认为都是只有一种方法,即容量为0,放0件物品,即 dp[i][0] = 1
但此题有例外,注意审题范围,物品的数值nums[i]是可以为0的,
如果有两个物品,物品0为0, 物品1为0,装满背包容量为0的方法有几种。
-
放0件物品
-
放物品0
-
放物品1
-
放物品0 和 物品1
此时是有4种方法。
其实就是算数组里有t个0,然后按照组合数量求,即 2^t 。
int zeroSize =0;
for(int i=0;i<nums.length;i++){
if(nums[i]==0) zeroSize++;
dp[i][0] = (int)Math.pow(2.0,zeroSize);
}
4.确定遍历顺序
在明确递推方向时,我们知道 当前值 是由上方和左上方推出。
那么我们的遍历顺序一定是 从上到下,从左到右。
因为只有这样,我们才能基于之前的数值做推导。
例如下图,如果上方没数值,左上方没数值,就无法推出 dp[2][2]
。
以从上到下 ,再从左到右遍历为例:
for(int i=1;i<nums.length;i++){
for(int j=1;j<=bagSize;j++){
}
}
5. 举例推导dp数组
AC代码如下:
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<Math.abs(target) || (sum+target)%2==1){
return 0;
}
int bagSize = (sum+target)/2;
int[][] dp = new int[nums.length][bagSize+1];
//初始化最上行
dp[0][0]=1;
if(nums[0]<=bagSize){
dp[0][nums[0]]=1;
}
//初始化最左列
int zeroSize =0;
for(int i=0;i<nums.length;i++){
if(nums[i]==0) zeroSize++;
dp[i][0] = (int)Math.pow(2.0,zeroSize);
}
//递推关系
for(int i=1;i<nums.length;i++){
for(int j=1;j<=bagSize;j++){
if(j<nums[i]) dp[i][j] = dp[i-1][j];
else
dp[i][j] = dp[i-1][j]+dp[i-1][j-nums[i]];
}
}
return dp[nums.length-1][bagSize];
}
}
题目二:474. 一和零
474. 一和零
(https://leetcode.cn/problems/ones-and-zeroes/description/)
此题需要注意,m 和 n相当于是一个背包,两个维度的背包。而不是部分同学想的多重背包问题,本题其实是01背包问题。
不同长度的字符串就是不同大小的待装物品。
动规五部曲:
1.确定dp数组以及下标的含义
dp[i][j]
:最多有 i个0 和 j个1 的strs串的最大子集的大小为dp[i][j]
。
2.确定递推公式
dp[i][j]
可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。
dp[i][j]
就可以是 dp[i - zeroNum][j - oneNum] + 1
。
然后我们在遍历的过程中,取dpi的最大值。
所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)
;
3.dp数组如何初始化
01背包的dp数组初始化为0就可以。
因为物品价值不会是负数,初始为0,保证递推的时候dpi不会被初始值覆盖。
4.确定遍历顺序
这里有个点需要注意
别看dp数组是二维的就认为是01背包的二维形式,这里m,n都是背包,是01背包的一维形式(滚动数组),只是这个滚动数组是二维的而已。
在前面章节我们讲到了01背包为什么一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!
那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。
AC代码如下:
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,oneNum=0;
for(char c : str.toCharArray()){
if(c=='0') zeroNum++;
else oneNum++;
}
//遍历背包
//一维dp倒叙
for(int zero=m;zero>=zeroNum;zero--){
for(int one=n;one>=oneNum;one--){
dp[zero][one] = Math.max(dp[zero][one],dp[zero-zeroNum][one-oneNum]+1);
}
}
}
return dp[m][n];
}
}
赞赞~