LeetCode-494. Target Sum

Description

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.

Example 1

Input: nums is [1, 1, 1, 1, 1], S is 3. 
Output: 5
Explanation: 

-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

There are 5 ways to assign symbols to make the sum of nums be target 3.

Note

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

Solution 1(C++)

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

Solution 2(C++)

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int s) {
        int sum = accumulate(nums.begin(),nums.end(),0);        
        if(sum<s || (s+sum &1)) return 0;
        int target = (s + sum) >>1;

        vector<int> dp(target+1,0);
        dp[0] = 1;

        for(int i =0;i < nums.size();i++){
            for(int t = target; t>=nums[i];t--){
                dp[t] +=dp[t-nums[i]];
            }
        }
        return dp[target];
    }   
};

Solution 3(C++)

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        return helper(nums, nums.size()-1, S);
    }
private:
    int helper(vector<int>& nums, int index, int S){
        int cur=nums[index];
        if(index==0){
            if((cur != S) &&(cur != -S)) return 0;
            else if ((cur == S) && (cur == -S)) return 2;
            else return 1;
        }
        else{
            return helper(nums, index-1, S-cur) + helper(nums, index-1, S+cur);
        }
    }
};

后续更新

其他类似的题目可参考:

算法分析

解法一与解法二

分析题意,对于数组nums,要找到一部分为正,另一部分为负,二者之和为要求的S。设前一部分和为A,后一部分和为B,那么根据题意有:

A-B=S;

设nums总和为sum,则有:

A+B=sum;

二者联立即可求得:

A=(S+sum)/2;    B=(sum-S)/2;

所以,我们可以将问题转化为在数组nums中,找到若干元素的和为(S+sum)/ 2的所有可能集合数。对于这个问题,可以采用动态规划的方法求解,数组nums中某个元素为:nums[i],设(S+sum)/ 2 为newS,则有:

//对于某一个nums[i],dp表示不同元素组合的和等于j的组合个数:
dp[j] = dp[j-nums[i]]

//dp的初始值dp[0]等于1,因为组合之和为0,元素组合只有一种:不包含任何元素——空集
dp[0] = 1

//由于求的是nums中所有元素所有可能的组合,所以有:
for(int i=0; i<nums.size(); i++){
    dp[j] = dp[j] + dp[j-nums[i]];    //dp[j] += dp[j-nums[i]];
}

//然后就是重头戏:对dp数组的遍历。
for(int i=0; i<nums.size(); ++i){
    for(int j=newS; j>=nums[i]; --j){
        dp[j] += dp[j-nums[i]];
    }
}

关于对dp数组的遍历,是从大到小遍历,这个顺序我还不是弄得特别懂,对比:LeetCode-279. Perfect Squares
中,类似的问题,但其遍历顺序是从小到大。现在对这两个问题做一个简单的对比:

用硬币组合的问题来说明,比较浅显易懂。本题其实可以理解为:

15个1元硬币,任意组合,问组合成11元有多少种可能的组合?

而LeetCode-279. Perfect Squares中的问题可以理解为:

1、2、5元硬币,任意组合,问组合成11元最少需要多少枚硬币?

这里自然不对以上两个问题做细致的求解。而需要注意的两个问题的状态转移方程的不同,以及遍历顺序的不同。

前者求多少种可能的组合的问题的状态转移方程是:

for(int i=0; i<nums.size(); i++){
    dp[j] = dp[j] + dp[j-nums[i]];
}

后者求组合最少的元素的个数的状态转移方程是:

for(int i=0; i<nums.size(); i++){
    dp[j] = min(dp[j], dp[j-nums[i]]+1);
}

尝试分析一下遍历顺序从大到小与从小到大的区别:

  • 如果是从小到大遍历dp数组,那么对于最初的元素nums[0],dp中所有元素都能累加到。比如求解组合元素个数最少的问题,从小到大遍历,那么当只有nums[0],或者具体来说是1元硬币的时候,构成1元、2元···11元,分别的个数就是:1、2、···、11。然后有新的nums[1],也就是2元硬币,便会对所有dp中的元素进行更新,比如构成2元的:dp[2]=1,而非2,构成4元的:dp[4]=2,而非4。但是对于求解多少种组合情况的问题时,如果只有nums[0],即第一个1元硬币,那么只能构成1元,不能构成2元、3元···4元。也就是说对于dp[n],只有i>=n时,dp[n]才可能不等于0。
  • 如果是从大到小遍历dp数组,那么对于最初的元素nums[0],dp中只有dp[nums[0]]这个元素能累加到。而可考虑的组合元素个数越多,dp中能改变的元素也就越大。比如:对于求解多少种组合情况的问题时,如果采用从小到大的顺序遍历,那么第一个1元硬币,就可以构成1元、2元、···、11元了,这显然是不符合实际情况的。而应该是:i=0时,dp[1]=1,dp[2]、dp[3]、···、dp[11]都等于0。当i=1时,dp[1]=2、dp[2]=1、dp[3]=0、···、dp[11]=0,这样才行。

上面说的比较啰嗦,原因也是自己对于这个问题理解不深,没有上升到数学层面的高度。但是其中意思还是可以简单概括一下:

  1. 主要对比了两种问题:(1)求组合的个数、(2)求组合中元素的个数。都是要让组合满足某种条件(组合的和为特定值)。只是后者可以单一元素构成组合,前者需要多个元素构成组合。
  2. 问题2从一开始一个元素就可能满足组合条件,但是问题1必须等到多个元素才可能满足组合条件
  3. 所以对于问题2这种,从小到大遍历,能在单一元素可用时,对dp数组中所有元素都更新;
  4. 对于问题1这种,从大到小遍历,能满足多个元素累加起来,才能对dp数组中某些特定元素进行更新。

关于动态规划算法是一种常见的有难度的算法题,目前也仅在这一篇博客中进行了简单的整理。还需要多做一些题目,多多积累经验。

解法三

减而治之的方法解决问题,也是需要好好掌握的方法。

程序分析

略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值