动态规划第七篇:01背包问题(目标和 + 一和零)
Posted Java架构师(公众号:毛奇志)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划第七篇:01背包问题(目标和 + 一和零)相关的知识,希望对你有一定的参考价值。
494. 目标和
问题描述
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
输入:nums: [1, 1, 1, 1, 1], S: 3 输出:5 解释:
-1+1+1+1+1 = 3 +1-1+1+1+1 = 3 +1+1-1+1+1 = 3 +1+1+1-1+1 = 3 +1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
提示:
数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。
抽象成01背包问题
本题要如何使表达式结果为target,
既然为target,那么就一定有 left组合 - right组合 = target。
left + right等于sum,而sum是固定的。
公式来了, left - (sum - left) = target -> left = (target + sum)/2 。
target是固定的,sum是固定的,left就可以求出来。
此时问题就是在集合nums中找出和为left的组合。
动态规划01背包问题
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum =0;
for (int i=0;i<nums.length;i++)
sum = sum + nums[i];
if (target > sum ) return 0;
if (( target + sum )%2==1) return 0;
int bigSize = ( target + sum )/2;
int[] dp=new int[bigSize+1]; // int数组初始化全部为0 为什么数组长度为 bigSize+1,因为内循环变量的时候 int j=bigSize;i>=0; 一共 [0,bigSize] bigSize+1 个
dp[0]=1; // 这个是迭代的基础,对于计算总数的时候
for(int i=0;i<nums.length;i++){
for (int j=bigSize;j>=nums[i];j--) { // 虽然j-- 很笨,但是只能这么用 不断减少背包容量
dp[j] = dp[j] + dp[j-nums[i]]; // j-nums[i] 不能数组越界 计算总数的都是这个dp状态方程
}
}
return dp[bigSize]; // dp长度为 bigSize+1 ,这就是最后一个元素
}
}
一维数组改成二维数组
1、新增第一个维度是 物体 数量; 先dp[i][0]=1
业务初始化,而后才是二维dp数组的模板初始化 i=0
2、对于 i=0 初始化 初始化要按照一维数组的条件来,是 for (int j = bigSize; j >= nums[0]; j--)
,也可以是 for (int j = bigSize; j >= 0; j--) if...else...
一个意思;
3、第一个for循环 i 从1开始,第二个for循环可以反向,也可以正向
4、循环内部要if else 分别处理 状态方程第一维,是 dp[i][j] = dp[i-1][j] + dp[i-1][j - nums[i]];
不是 dp[i][j] = dp[i][j] + dp[i][j - nums[i]];
5、最后返回,第一个维度为 物体数量 最后一个,第二个维度表示 容量 target
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int i = 0; i < nums.length; i++)
sum = sum + nums[i];
if (target > sum) return 0;
if ((target + sum) % 2 == 1) return 0;
int bigSize = (target + sum) / 2; // 得到背包重量容量 背包价值就是target,不用管了,只要管好背包重量容量就好
int[][] dp = new int[nums.length][bigSize + 1]; // int数组初始化全部为0 为什么数组长度为 bigSize+1,因为内循环变量的时候 int j=bigSize;i>=0; 一共 [0,bigSize] bigSize+1 个
// 取代 dp[0]=1;
for (int i = 0; i < nums.length; i++)
dp[i][0] = 1;
// 初始化第一行不是取代 dp[0]=1; 这个是迭代的基础,对于计算总数的时候 装满容量为0的背包,有1种方法,就是装0件物品
for (int j = bigSize; j >= nums[0]; j--) {
dp[0][j] = dp[0][j] + dp[0][j - nums[0]]; // j-nums[i] 不能数组越界 计算总数的都是这个dp状态方程
}
for (int i = 1; i < nums.length; i++) {
for (int j = bigSize; j >= 0; j--) { // 虽然j-- 很笨,但是只能这么用 不断减少背包容量
if (j >= nums[i])
dp[i][j] = dp[i-1][j] + dp[i-1][j - nums[i]]; // j-nums[i] 不能数组越界 计算总数的都是这个dp状态方程
else
dp[i][j] = dp[i-1][j]; // 必须用上一行控制下一行
}
}
return dp[nums.length - 1][bigSize]; // dp长度为 bigSize+1 ,这就是最后一个元素
}
}
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int i = 0; i < nums.length; i++)
sum = sum + nums[i];
if (target > sum) return 0;
if ((target + sum) % 2 == 1) return 0;
int bigSize = (target + sum) / 2; // 得到背包重量容量 背包价值就是target,不用管了,只要管好背包重量容量就好
int[][] dp = new int[nums.length][bigSize + 1]; // int数组初始化全部为0 为什么数组长度为 bigSize+1,因为内循环变量的时候 int j=bigSize;i>=0; 一共 [0,bigSize] bigSize+1 个
for (int i = 0; i < nums.length; i++)
dp[i][0] = 1;
// 初始化第一行不是取代 dp[0]=1; 这个是迭代的基础,对于计算总数的时候 装满容量为0的背包,有1种方法,就是装0件物品
for (int j = bigSize; j >= 0; j--) {
if (j >= nums[0])
dp[0][j] = dp[0][j] + dp[0][j - nums[0]]; // j-nums[i] 不能数组越界 计算总数的都是这个dp状态方程
else
dp[0][j] = dp[0][j];
}
for (int i = 1; i < nums.length; i++) {
for (int j = bigSize; j >= 0; j--) { // 虽然j-- 很笨,但是只能这么用 不断减少背包容量
if (j >= nums[i])
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]; // j-nums[i] 不能数组越界 计算总数的都是这个dp状态方程
else
dp[i][j] = dp[i - 1][j]; // 必须用上一行控制下一行
}
}
return dp[nums.length - 1][bigSize]; // dp长度为 bigSize+1 ,这就是最后一个元素
}
}
问题1:一维dp数组的含义和二维dp数组的含义?
回答1:
一维dp数组:填满j(包括j)这么大容积的包,有dp[i]种方法;
二维dp数组:使用 下标为[0~i]
的 nums[i] 能够凑满j(包括j)这么大容量的包,有dp[i][j]
种方法。
问题2:dp状态方程的理解?
回答2:
一维:dp[j] = dp[j] + dp[j-nums[i]];
对于容量为j背包,之前的方法数是dp[j],现在来了num[i],所以容量数 是 原来的dp[j] (j 容量的背包中不放入num[i])+ dp[j-nums[i]] (j 容量的背包中放入num[i])。
二维:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
对于容量为j背包,之前的方法数是dp[i-1][j],现在来了num[i],所以容量数 是 原来的dp[i-1][j] (j 容量的背包中不放入num[i])+ dp[i-1][j-nums[i]] (j 容量的背包中放入num[i])。
问题3:dp[0]的初始化为1 ?
回答3:从递归公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递归结果将都是0。dp[0] = 1,理论上也很好解释,装满容量为0的背包,有1种方法,就是装0件物品。dp[j]其他下标对应的数值应该初始化为0,从递归公式也可以看出,dp[j]要保证是0的初始值,才能正确的由dp[j - nums[i]]推导出来。
问题4:dp数组为什么设置为 bigSize+1 ?
回答4:因为内循环变量的时候 int j=bigSize;j>=0;j--
,所以, 一共 [0,bigSize] bigSize+1 个。
问题5:bigSize的意思?
回答5:bigSize的意义就是背包重要容量,就是为正数的数字的数字和。
474.一和零
问题描述
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的大小,该子集中 最多 只能有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
示例 1:
输入:strs = [“10”, “0001”, “111001”, “1”, “0”], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {“10”,“0001”,“1”,“0”} ,因此答案是 4 。
其他满足题意但较小的子集包括 {“0001”,“1”} 和 {“10”,“1”,“0”} 。{“111001”} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
示例 2:
输入:strs = [“10”, “0”, “1”], m = 1, n = 1
输出:2
解释:最大的子集是 {“0”, “1”} ,所以答案是 2 。
提示(取值范围):
1 <= strs.length <= 600
1 <= strs[i].length <= 100
strs[i] 仅由 ‘0’ 和 ‘1’ 组成
1 <= m, n <= 100
抽象成01背包问题
彻底理解这个题目,一个图就好了,如下:
问题1:二维dp数组的含义?
回答1:dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
问题2:dp状态方程的理解?
回答2:
二维:dp[i][j]=Math.max(dp[i][j],dp[i-zeroNum][j-oneNum]+1);
两个维度都是从一个一个字符来判断,当前的结果数存放在dp[i][j]里面,对于新来的 zeroNum oneNum,有两个状态供选择,
状态1:当结果中不选取 zeroNum oneNum, 结果就是仍然是 dp[i][j]
状态2:当结果中选取 zeroNum oneNum ,结果为 dp[i-zeroNum][j-oneNum]+1
两个状态、两种情况中选择比较大的。
对比标志的状态方程 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
发现,字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。所以,这就是一个典型的01背包! 只不过物品的重量有了两个维度而已。
问题3:dp数组为什么设置为 m+1 n+1 ?
回答3:因为遍历的时候要用到 [m,0] [n,0]
问题4:为什么两层循环要用到 [m,0] [n,0]
回答4:因为对于新来的字符串数组的字符串,要全部更新dp数组。
动态规划01背包问题
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp=new int[m+1][n+1]; // 全部初始化为0
for (String str:strs) {
char[] chars=str.toCharArray();
int zeroNum=0;int oneNum=0;
for (char ch:chars){
if ('0'==ch)
zeroNum++;
else
oneNum++;
}
for(int i=m;i>=zeroNum;i--){ // 遍历范围从 [m,0] 所以定义长度为m+1
for (int j=n;j>=oneNum;j--){ // 遍历范围从 [n,0] 所以定义长度为n+1
dp[i][j]=Math.max(dp[i][j],dp[i-zeroNum][j-oneNum]+1);
}
}
}
return dp[m][n];
}
}
换成二维数组,现在已经是二维数组了…
换成三维数组太复杂,不涉及。
以上是关于动态规划第七篇:01背包问题(目标和 + 一和零)的主要内容,如果未能解决你的问题,请参考以下文章