1.什么是0-1背包问题
0-1背包问题:
2.目标和
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
2.1递归法
我们可以递归搜索每一种可能,即每一步的结果都有两种 ,sum+num[i] 或者 sum-nums[i] ,递归出口为 当i>=nums.size()时去判断sum是否等于target
class Solution {
public:
void _findTargetSumWays(vector<int> &nums,int &target,int sum,int sub,int &count)
{
if(sub>=nums.size())
{
if(sum==target)
count++;
return ;
}
_findTargetSumWays(nums,target,sum-nums[sub],sub+1,count);
_findTargetSumWays(nums,target,sum+nums[sub],sub+1,count);
}
int findTargetSumWays(vector<int>& nums, int target) {
int count=0;
int sum=0;
_findTargetSumWays(nums,target,sum,0,count);
return count;
}
};
2.2动态规划法
根据题意可知我们拿取一个数据可以为其添加+或者-,因此我们可以将所有的正数加起来得到 s1,将所有的负数加起来得到 s2
-> s1-s2=target 左右两边加上数组内所有元素的和sum(s1+s2)-> s1+s2 + s1-s2 =sum +target -> s1 = (sum+target)/2;
由上述推导出公式 s1= (sum+target)/2 ,所以原问题转化为从数组之中寻找元素构成s1
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
//原问题转换:s1-s2=target -> 2s1=target + sum -> s1=(target+sum) /2
//即从nums之中选取s1个数 = (target+sum)/2; sum(nums之中所有元素的总和)
int sum=0;
int count=0;
for(auto &e:nums)
{
sum+=e;
}
if(target>sum||target<-sum)
return count;
if((sum+target)%2!=0)
return 0;
sum=(sum+target)/2;//得到"背包容量"
vector<vector<int>> dp(nums.size()+1,vector<int>(sum+1,0));
dp[0][0]=1;//0个数字组成0
for(int i=0;i<nums.size();i++)
{
for(int j=0;j<=sum;j++)
{
if(j-nums[i]>=0)
dp[i+1][j]=dp[i][j-nums[i]]+dp[i][j];//拿了和没拿的总和
else
dp[i+1][j]=dp[i][j];//拿不了
}
}
return dp[nums.size()][sum];
}
};
空间优化:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
//原问题转换:s1-s2=target -> 2s1=target + sum -> s1=(target+sum) /2
//即从nums之中选取s1个数 = (target+sum)/2; sum(nums之中所有元素的总和)
int sum=0;
int count=0;
for(auto &e:nums)
{
sum+=e;
}
if(target>sum||target<-sum)
return count;
if((sum+target)%2!=0)
return 0;
sum=(sum+target)/2;//得到"背包容量"
vector<int> dp(sum+1,0);
dp[0]=1;
for(int i=0;i<nums.size();i++)
{
for(int j=sum;j>=0;j--)
{
if(j-nums[i]>=0)
dp[j]=dp[j]+dp[j-nums[i]];
}
}
return dp[sum];
}
};
3.分割等和子集
题目链接
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
for(auto &e:nums)
{
sum+=e;
}
if(sum%2!=0)
return false;
sum/=2;
vector<vector<bool>> dp(nums.size()+1,vector<bool>(sum+1,false));
dp[0][0]=true;
for(int i=0;i<nums.size();i++)
{
for(int j=0;j<=sum;j++)
{
if(j-nums[i]>=0)//可拿、可不拿
dp[i+1][j] = dp[i][j-nums[i]] || dp[i][j];
else//不可拿
dp[i+1][j] = dp[i][j];
}
}
return dp[nums.size()][sum];
}
};
空间优化:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
for(auto &e:nums)
{
sum+=e;
}
if(sum%2!=0)//不能均分
return false;
sum/=2;
//从数组之中选取元素组成sum;
vector<int> dp(sum+1,false);
dp[0]=true;
for(int i=0;i<nums.size();i++)
{
for(int j=sum;j>=0;j--)
{
if(j-nums[i]>=0)//表示可以拿
dp[j]=dp[j-nums[i]]||dp[j];
}
}
return dp[sum];
}
};
4.求正数数组的最小不可组成和
题目链接
给定一个全是正数的数组arr,定义一下arr的最小不可组成和的概念: 1,arr的所有非空子集中,把每个子集内的所有元素加起来会出现很多的值,其中最小的记为min,最大的记为max; 2,在区间[min,max]上,如果有一些正数不可以被arr某一个子集相加得到,那么这些正数中最小的那个,就是arr的最小不可组成和; 3,在区间[min,max]上,如果所有的数都可以被arr的某一个子集相加得到,那么max+1是arr的最小不可组成和; 举例: arr = {3,2,5} arr的min为2,max为10,在区间[2,10]上,4是不能被任何一个子集相加得到的值中最小的,所以4是arr的最小不可组成和; arr = {3,2,4} arr的min为2,max为9,在区间[2,9]上,8是不能被任何一个子集相加得到的值中最小的,所以8是arr的最小不可组成和; arr = {3,1,2} arr的min为1,max为6,在区间[2,6]上,任何数都可以被某一个子集相加得到,所以7是arr的最小不可组成和; 请写函数返回arr的最小不可组成和。
class Solution {
public:
/**
* 正数数组中的最小不可组成和
* 输入:正数数组arr
* 返回:正数数组中的最小不可组成和
*/
int getFirstUnFormedNum(vector<int> arr, int len) {
if (len == 1)
return arr[0] + 1;
//算出最小、最大值
int min = arr[0];
int max = 0;
for (auto&e : arr)
{
min = fmin(min, e);
max += e;
}
//cout << min << " " << max << endl;
//判断从[min+1,max-1]中是否有arr中元素不能够成的元素 -> 01背包问题
vector<vector<bool>>dp(len + 1, vector<bool>(max + 1, false));
//dp[i][j]表示从arr中选取i个元素,看是否能够构成j
dp[0][0] = true;//0个元素可以构成0
for (int i = 0; i<len; i++)
{
for (int j = min; j<=max; j++)
{
if(j-arr[i]==0)
dp[i+1][j]=true;
else if (j - arr[i] >= 0)//表示可拿可不拿
dp[i + 1][j] = dp[i][j] || dp[i][j - arr[i]];//dp[i][j]表示没有拿,dp[i][j-a[i]]表示拿了
else//不可以拿
dp[i + 1][j] = dp[i][j];
}
}
for (int i = min + 1; i<max; i++)
{
if (dp[len][i] == false)
return i;
}
return max + 1;
}
};
空间优化:
class Solution {
public:
/**
* 正数数组中的最小不可组成和
* 输入:正数数组arr
* 返回:正数数组中的最小不可组成和
*/
int getFirstUnFormedNum(vector<int> arr, int len) {
if (len == 1)
return arr[0] + 1;
//算出最小、最大值
int min = arr[0];
int max = 0;
for (auto&e : arr)
{
min = fmin(min, e);
max += e;
}
vector<bool>dp(max+1,false);
for(int i=0;i<len;i++)
{
for(int j=max;j>=min;j--)
{
if(arr[i]==j)//直接过来
dp[j]=true;
else if(j-arr[i])
dp[j]=dp[j-arr[i]]||dp[j];
}
}
for(int i=min;i<=max;i++)
{
if(!dp[i])
return i;
}
return max+1;
}
};