494 Target Sum

题目

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:
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.

解法一:递归

第一个想到的方法是top down recursion,从后往前想,我们要知道11111有几种方法能得到5,那么就是要知道1111有几种方法能得到4或者6……这样对每一个数都会有两种讨论情况,时间复杂度为O(2^n)。经过实际操作得知,每一层需要讨论的数可能会有重复,所以需要用一个数据结构来储存已经计算出的结果。这个数据结构必须能告诉我们:1、我们在讨论的是哪层;2、此时的sum是多少;3、到达这种sum有几种可能。我用了vector<unordered_map>进行储存,vector的index告诉我们是到了哪一层,map的key告诉我们sum是多少,value告诉我们到达sum有几种可能。另外,有一个特殊情况,即对于0,既要考虑+0,也要考虑-0。代码如下:

class Solution {
// recursion with memoization
// time: O(2^n)
// space: O(2^n)
private:
    int recursion(vector<int>& nums, int s, int cur, vector<unordered_map<int, int>>& mem)
    {
        if (mem[cur].find(s) != mem[cur].end()) return mem[cur][s];
        if (!cur) return mem[cur][s] = (nums[cur] == s) + (nums[cur] == -s);
        return mem[cur][s] = recursion(nums, s + nums[cur], cur - 1, mem) + recursion(nums, s - nums[cur], cur - 1, mem);
    }

public:
    int findTargetSumWays(vector<int>& nums, int S) 
    {
        vector<unordered_map<int, int>> mem(nums.size()); 
        return recursion(nums, S, nums.size() - 1, mem);
    }
};

解法二:递归

这种recursion+memoization的题目,很有可能是可以用dp来做的。dp的问题需要一颗敏锐的眼睛,关键在于看出什么是subproblem,dp[i][j]代表什么,递推式是什么。这道题我自己没有看出来,借助了评论区答案,又是一个背包问题。嗯,感觉自己不太擅长这个还是~放一个reference在这里~ http://love-oriented.com/pack/Index.html

那一步步来,先看最基本的二维dp。用dp[i][j]来表示用前i个数字来组成的sum=j(背包问题的经典模式)。对于第i个数字,我们可以取正和负两种情况,所以dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]]。base case我们取dp[0][0]=1,这个是我在自己举例递推的时候make sense的。假如j可以取负数的话,我们用11111和5来举例:dp[1][-5]=dp[0][-6]+dp[0][-4]=0……dp[1][-1]=dp[0][0]+dp[0][-2]=1.可以看出只有base case是这样子,递推才会make sense,不然一路下来都是0。。。那这时候因为j必须>=0,我们就将j加上所有数字的总和sum,使得j的取值范围,从-sum~sum转化为了0~2*sum。另外,由于我们所建立的二维数组的长是nums.size(),宽是2*sum+1,必须要保证j的取值范围在这之内(所以上面那个例子不太合适)。而对于S本身就比sum大或者-sum小的情况,要提前terminate,不然返回的时候会出现runtime error。代码如下:

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

我们看到每一次dp[i]的情况都取决于dp[i-1]的情况,那么我们可以将space减少到一维数组。如果我们单单把dp[i][j]变成dp[j]的话,会发现每次update j的时候,会调用我们刚计算过的数据,结果就不对了。我们想要调用真正的i-1那一行所计算出来的值,而不是掺杂了我们刚计算出来的结果。所以我们可以再建立一个数组来保存i-1那一行的结果。代码如下:

int findTargetSumWays(vector<int>& nums, int S) 
    {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        vector<int> dp(2 * sum + 1, 0);
        if (S > sum || S < -sum) return 0;
        dp[sum] = 1;
        for (int i = 1; i < nums.size() + 1; ++i)
        {
            vector<int> next(2 * sum + 1, 0);
            for (int j = 0; j < 2 * sum + 1; ++j)
            {
                if (j + nums[i - 1] < 2 * sum + 1)
                {
                    next[j] += dp[j + nums[i - 1]];
                }
                if (j - nums[i - 1] >= 0)
                {
                    next[j] += dp[j - nums[i - 1]];
                }
            }
            dp = next;
        }
        return dp[S + sum];
    }

另外还有一种形式可以把上面的俩if合并成一个。这两个if在衡量的是,j之前的那个sum,是否在0~2*sum区间内,

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值