leetcode 416,494

这两天在刷leetcode的时候,刚好遇到了背包问题(416,494)。这里把这两天的收获写一下。

背包问题

背包问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。

1.最基础的背包问题(0/1背包问题):有N件物品和一个容量为V的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
0/1背包问题的最显著的特征就是,每种物品只有一件,放入背包之后,就没有第二件可放了。

除了0/1背包问题,还有完全背包问题:有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

以及多重背包问题:有N种物品和一个容量为V的背包。第i种物品最多有n件可用,每件体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

这几种背包问题的解法是大同小异的,网络上更多的解法都是针对0/1背包问题的,我目前也只研究了这一种背包问题的解决方法。其他问题可以慢慢再学习。
2.0/1背包问题的解法
0/1背包问题的解法一般有

暴力法:对于有n种可选物品的0/1背包问题,其解空间由长度为n的0-1向量组成,可用子集数表示。在搜索解空间树时,深度优先遍历,搜索每一个结点,无论是否可能产生最优解,都遍历至叶子结点,记录每次得到的装入总价值,然后记录遍历过的最大价值。
暴力法的时间复杂度:O(2^n),可以说是非常慢了,不到万不得已还是别这么求解。
动态规划:这里贴上一张我在网上找到的图片。转载自:https://www.cnblogs.com/xym4869/p/8513801.html
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

leetcode 416

leetcode 416的题目是这样的:Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.

Note:
Each of the array element will not exceed 100.
The array size will not exceed 200.

给定一个只包含正数的非空数组,找到这个数组的一个子集,这个子集中的元素之和等于原数组元素之和的一半。

注释:
每个元素的大小不会超过100
数组长度不会超过200

既然都说我们要用解决背包问题的思想来解决这个问题,那现在就要思考一下如何把问题转化。
我们把数组中的每个数当作要向背包里装的物品,第i个物品的重量和价值都是nums[i],背包的容量为sum(num[i])/2。那么问题就等价于:从数组中选出若干个数(物品)放进背包当中,力求将背包塞满(即放入背包的物品的重量之和尽量接近sum(num[i])/2.

我们可以用leetcode上这道题的第一个测试用例来举例。按照0/1背包问题的标准解法来讲,其表格是这样的:
在这里插入图片描述
最后回溯,就可以的到结果[1,5,5]

代码是这样的:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum;
        sum=accumulate(nums.begin(), nums.end(), 0);
        if(sum%2==1)return false;
        int aim=sum/2,m=nums.size()+1,n=aim+1;  
        int** dp = new int*[m];
        int x[m-1];
        for(int i=0;i<m;++i){
            dp[i]=new int[n];
        }
        for(int i=0;i<n;++i){
            dp[0][i]=0;
        }
        for(int i=0;i<m;++i){
            dp[i][0]=0;
        }
        for(int i=1;i<m;++i){
            for(int j=1;j<n;++j){
                if(j<nums[i-1]){
                    dp[i][j]=dp[i-1][j];
                }else{
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
                }               
            }
        }
        if(dp[m-1][n-1]==aim)
            return true;
        else
            return false;
    }
};

leetcode 494

题目:You are given a list of non-negative integers, a1, a2, …, an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol.

Find out how many ways to assign symbols to make sum of integers equal to target S.

Note:
The length of the given array is positive and will not exceed 20.
The sum of elements in the given array will not exceed 1000.
Your output answer is guaranteed to be fitted in a 32-bit integer.

就是给定n个非负整数和一个目标值S,要通过对这些数进行+和-来得到最终的S,而且所有的数必须要用上。求所有的+和-的组合的可能性。

百度到的其他答案都说这是一个背包问题。我看了一下,感觉的确是一种背包问题的变形。只不过原始的0/1背包问题是选择若干个物品放在一定容量的背包里,使该背包中的物品的价值最大;而现在的问题变成了:所有的物品都要放到背包里,背包的容量就是给定的目标值,而每个物品的重量(或者说权重/体积)则有正有负。

显然不能生搬硬套。

我们依然用一个二维数组dp来进行求解。dp[i][j]表示利用前i个物品得到总体积为j得组合数。则有:
dp[i][j]=dp[i-1][j-nums[i]+dp[i-1][j+nums[i]];

除了这个递推公式外,我们为了方便在写程序得时候理清下标关系,我们把从-sum(nums)到sum(nums)得这个范围换到从0到2sum+1。
同时,由于每一层循环只针对矩阵dp的两行,那么可以把O(n*sum)的空间压缩成O(sum),不断滚动数据.
最终的代码是:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        int n = nums.size(),sum = 0;
        for(int i=0;i<n;++i){
            sum+=nums[i];
        }
        int m=2*sum+1;
        int *pre=new int [m];
        int *cur=new int [m];
        cout<<"123"<<endl;
        for(int i=0;i<m;++i){
            pre[i]=0;cur[i]=0;
        }
        pre[sum]=1;
        for(int i=0;i<n;++i){
            for(int j=0;j<m;++j){
                cur[j]=0;
                if(j-nums[i]>=0){
                    cur[j]+=pre[j-nums[i]];                    
                }                    
                if(j+nums[i]<m){
                    cur[j]+=pre[j+nums[i]];
                }   
            }
            for(int j=0;j<m;++j)
                pre[j]=cur[j];
        }
        if(S>=INT_MAX||S<=INT_MIN)
            return 0;
        if(sum+S>INT_MAX||sum+S<INT_MIN)
            return 0;
        if(sum+S>=m||sum+S<0)
            return 0;
        return cur[sum+S];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值