题目如下:

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:

1
2
3
4
5
6
7
8
9
10
> 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.

题目大意

给定一个正数数组以及一个整数S,现在有两个符号+-,对于数组里面的每一个数字,你可以选择+或者-作为它的新符号,找出所有选择符号的方式,使这些数字和为S。简单来说,给定一个数组,每个数可以加或者减,求出所有能使这些数字加减和为S的组合数。如{1,1,1,1,1},S为3,那么一共有5种方法使得这些数字和为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
+1+1+1+1-1 = 3

思路

假设数组中所有数字之和为sum。根据使用的符号不同,我们可以将数组内数字分为2组PN,如数组{1,2,3},目标为0,那么显然有如下选择方式:

+1+2-3 = 0

那么其中P={1,2},N={3}。那么sum(P)-sum(N)=S,并且sum(P)+sum(N)=sum。由此可以推出S+sum=sum(P)-sum(N)+sum(P)+sum(N)=2*sum(P)

这样我们就把原来的问题转换成了一个新的问题,在整个数组中,能否找到一些数字的和为(S+sum)/2,这就成了一个最经典的0-1背包问题,这样的话就简单了很多。

这里有一个需要注意的地方,(S+sum)/2这个数必须为偶数,否则无法得到,其次,S也必须小于sum,否则也无法求得。

这个算法的空间复杂度不定,取决于Ssum,不过题目限制了sum < 1000,那么空间复杂度为O(sum)。至于时间复杂度也取决于sum,时间复杂度是O(sum*N2)。Java版的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public int findTargetSumWays(int[] nums, int S) {
int sum = 0;
for (int i = 0; i < nums.length; i ++){
sum += nums[i];
}
if (S > sum || (S + sum) % 2 != 0){
return 0;
}
sum = (S + sum) / 2;
int [] dp = new int[sum + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i ++){
for (int j = sum; j >= nums[i]; j --){
dp[j] += dp[j - nums[i]];
}
}
return dp[sum];
}

提交结果如下:

运行结果
运行结果

总结

最近在刷Leetcode上的tag为动态规划的题,做了那么多其实动态规划的思想一直没变,最重要的是找到状态、状态转移公式以及转移状态的条件。当然,有的时候还需要一些数学推导,把当前不熟悉的问题转化成熟悉的问题。
```