动态规划之——0-1背包问题

1.什么是0-1背包问题

0-1背包问题:
假设现在有i件物品、每件物品的价值和重量不一样。有容量为c的背包、求背包能装取的最大价值是多少?

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;
		
	}
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值