题目
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例
输入: nums: [1, 1, 1, 1, 1], S: 3
输出: 5
解释:
-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
一共有5种方法让最终目标和为3。
注意
- 数组非空,且长度不会超过20。
- 初始的数组的和不会超过1000。
- 保证返回的最终结果能被32位整数存下。
思路
- 有加减两种操作,数组中的数都为正,如果全部相加,令和为sum。如果全部相减,和为-sum。这个就是数组中元素加加减减的边界,-sum~sum。
- j表示-sum到sum中的一个数,现在求前i个数有几种方式得到j。因为数组的索引不能为负数,但是j有可能为负数,所以用 d p [ j + s u m ] [ i ] dp[j+sum][i] dp[j+sum][i]表示前i个数加减操作得到j的方法数。
- d p [ j + s u m ] [ i ] = d p [ j − n u m s [ i − 1 ] + s u m ] [ i − 1 ] + d p [ j + n u m s [ i − 1 ] + s u m ] [ i − 1 ] dp[j+sum][i]=dp[j-nums[i-1]+sum][i-1]+dp[j+nums[i-1]+sum][i-1] dp[j+sum][i]=dp[j−nums[i−1]+sum][i−1]+dp[j+nums[i−1]+sum][i−1]。要注意 j − n u m s [ i − 1 ] j-nums[i-1] j−nums[i−1]不能小于-sum。 j + n u m s [ i − 1 ] j+nums[i-1] j+nums[i−1]不能大于sum。
代码
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for ( auto& n : nums )
sum += n;
if ( sum < abs( S ) ) return 0;
int dp[sum*2+1][nums.size()+1];
memset( dp, 0, sizeof(dp) );
dp[sum][0] = 1;
for ( int i = 1; i <= nums.size(); ++i ) {
for ( int j = -sum; j <= sum; ++j ) {
int sub = j - nums[i-1] + sum;
int add = j + nums[i-1] + sum;
if ( sub >= 0 )
dp[j+sum][i] += dp[sub][i-1];
if ( add <= 2 * sum )
dp[j+sum][i] += dp[add][i-1];
}
}
return dp[S+sum][nums.size()];
}
};