leetcode-分割等和子之0-1背包问题

0-1背包问题

背包问题是经典的dp问题,可以分为0-1背包和完全背包,这里我们讨论0-1背包问题;
问题场景一般是:
背包最大容量为V,物品数量为N;
体积数组:v[0…N-1] = {…}
价值数组:c[0…N-1] = {…}
求解能装下的最大价值,每个物品要么拿要么不能,只能拿一次;
我们定义dp二维数组:dp[i][j]表示面对前i个物品,背包容积为j时,能获得的最大价值
那么状态转移方程:

dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+c[i])

那么主要代码可以表示为:

for(int i=1;i<v.size();i++)
{
	for(int j=0;j<=V;j++)
	{
		dp[i][j] = dp[i-1][j];
		if(j-v[i]>=0)
			dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]+c[i]);
	}
}

还可以进一步简化为一维数组,但是要注意了正推和逆推会导致完全背包和0-1背包!
我们来看正推:

for(int i=1;i<v.size();i++)
{
	for(int j=v[i];j<=V;j++)
	{
		if(j-v[i]>=0)
			dp[j] = max(dp[j],dp[j-v[i]+c[i]);
	}
}

注意这是完全背包!这会有可能再次去哪之前拿过了的物品!因为是由前往后推的,前面对某一物品拿取的结果会覆盖掉之前没有拿的结果,导致推到后面的时候用了已经拿过的结果作为记忆而导致重复拿取;

0-1背包拿了之后不能再拿,因此只能用逆推!

for(int i=1;i<v.size();i++)
{
	for(int j=V;j>=v[i];j++)
	{
		if(j-v[i]>=0)
			dp[j] = max(dp[j],dp[j-v[i]]+c[i]);
	}
}

分割等和子集

在这里插入图片描述子集和的问题其实是背包问题的一种变体,这种问题一般本质上是问一个数列是否能有一个子集使得子集的和等于某个期望的数!而一个数到底放不放入子集取决于不放入是否能达到期望和或放入的时候能否达到期望和!

可以先求数组的和,若为奇数那么肯定为false;
然后可以以sum/2为目标,每个数可以分为拿和不拿,就像01背包一样,定义dp数组:
dp[i][j]表示,对于前i个数,子集和为j的可行性,可行则为1,不可行为0
那么得到状态转移方程:

dp[i][j] = dp[i-1][j] | dp[i-1][j-nums[i]]

代码:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        if(nums.size()==1)
            return false;
        int sum = 0;
        for(int i=0;i<nums.size();i++)
            sum += nums[i];
        if(sum%2 == 1)
            return false;
    
        sum = sum/2;
        cout<<sum;
        vector<vector<int>> dp(nums.size(),vector<int>(sum+1,0));
        dp[0][0] = dp[0][nums[0]] = 1;
        for(int i=1;i<nums.size();i++)
        {
            for(int j=0;j<=sum;j++)
            {
                dp[i][j] = dp[i-1][j];
                if(j-nums[i]>=0)
                    dp[i][j] = dp[i-1][j]|dp[i-1][j-nums[i]];
            }
        }

        if(dp[nums.size()-1][sum] == 0)
            return false;
        else
            return true;
    }
};

简化为一维dp:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        if(nums.size()==1)
            return false;
        int sum = 0;
        for(int i=0;i<nums.size();i++)
            sum += nums[i];
        if(sum%2 == 1)
            return false;
    
        sum = sum/2;
        cout<<sum;
        vector<int> dp(sum+1,0);
        dp[0] = dp[nums[0]] = 1;
        for(int i=1;i<nums.size();i++)
        {
            for(int j=sum;j>=0;j--)
            {   
                if(j-nums[i]>=0)
                    dp[j] = dp[j]|dp[j-nums[i]];
            }
        }
        if(dp[sum]==0)
            return false;
        else
            return true;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值