Leetcode编程题解:494. Target Sum

来源:互联网 发布:怎样关闭136端口 xp 编辑:程序博客网 时间:2024/06/01 10:14

题目:

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: 5Explanation: -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 = 3There are 5 ways to assign symbols to make the sum of nums be target 3.

注意事项:

  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.

这道题的实现方法很多,代码如下:

方法一:DFS、

class Solution {public:    int findTargetSumWays(vector<int>& nums, int S) {        if(nums.size() == 0)        return 0;        dfs(nums, S, 0, 0);        return cnt;    }    void dfs(vector<int>& nums, int S, int k, int sum) {        if(k == nums.size()) {            if(S == sum)            cnt++;            return;        }        dfs(nums, S, k + 1, sum + nums[k]);        dfs(nums, S, k + 1, sum - nums[k]);    }private:    int cnt = 0;};

方法二:动态规划、

其实,每个数之前添加+或者-,就相当于这个数是选正数还是负数,就一01背包。

状态表示d[i][j],选过第i个数后,和为j的方案数。

转移方程d[i][j]=d[i1][jai]+d[i1][j+ai]。分别表示当前数前面是添+还是-,并且还要注意j可能为负数,因此要加一个偏移量ofs

const int maxn = 25;const int maxm = 5005;#define ofs 2003int d[maxn][maxm];class Solution {public:    int findTargetSumWays(vector<int>& nums, int S) {        if (S > 1000) return 0;        bool flag = !(nums[0]);        memset(d, 0, sizeof(d));        d[0][nums[0] + ofs] = d[0][-nums[0] + ofs] = 1;        for (int i = 1; i < nums.size(); i++) {            for (int j = -1000; j <= S + 1000; j++) {                d[i][j + ofs] = d[i - 1][j - nums[i] + ofs] + d[i - 1][j + nums[i] + ofs];                }        }        return flag == true ? d[nums.size() - 1][S + ofs] * 2 : d[nums.size() - 1][S + ofs];    }};

在Solution里面看到一个很好的算法。

转化问题

我们是在一些数前面添加+,一些数前面添加-。我们把添加+的数记为集合P,添加-的数记为集合N。
比如{1, 2, 3, 4, 5}, target = 3。如果我们这么添加:+1 - 2 + 3 - 4 + 5 = 3。则P = {1, 3, 5}, N = {2, 4}。
那么,我们可以得到如下公式:
(1)sum(P)sum(N)=target
(2)sum(P)+sum(N)=sum
(1)+(2)得到:2sum(P)=target+sum
所以,我们的问题就转化成了:在nums[]找一个子集P,使P的和为targt+sum2

求子集

那么现在问题可以这么描述:我们有一个数组nums,我们需要求解能够组合集合P的方案数,使P的和为S(S=targt+sum2)
就0 - 1背包
状态表示d[i,j],前i个数的和为j的方案数。d[i][j]=d[i1][jai]+d[i1][j+ai]
转移就分为第i个数是否加入我们的集合P。 d[i][j]=d[i1][jai]+d[i1][j+ai]
转移方程d[i][j]=d[i1][jai]+d[i1][j+ai]

d[i,j]=d[i1,j]+d[i1,jaid[i,j]=d[i1,j]+d[i1,jaid[i,j]=d[i1,j]+d[i1,jaid[i,j]=d[i1,j]+d[i1,jaid[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i][j]=d[i1][jai]+d[i1][j+ai]d[i, j] = d[i - 1, j] + d[i - 1, j - ai]

class Solution {public:    int findTargetSumWays(vector<int>& nums, int s) {        int sum = accumulate(nums.begin(), nums.end(), 0);        return sum < s || (s + sum) & 1 ? 0 : subsetSum(nums, (s + sum) >> 1);     }       int subsetSum(vector<int>& nums, int s) {        int dp[s + 1] = { 0 };        dp[0] = 1;        for (int n : nums)            for (int i = s; i >= n; i--)                dp[i] += dp[i - n];        return dp[s];    }};

细节

  1. 在算法1中,注意这道题有个坑点就是:第一个数为0的时候,其实有两种方案+0和-0,但是我们代码写的是d[0][0]=1,因此,设置一个flag,第一个为0的时候我们的结果应该x2。并且后续若为0不用x2。
  2. 在算法2中,滚动数组优化空间。

原创粉丝点击