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
11
12
13 > 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.
题意:给出一个数组,每个数前面可以添加正负号的其中一种,凑成一个表达式,求表达式之和刚好等于target有多少种可能性。
一开始采用的是递归的写法,类似于二叉树的打印路径的思想。当走到最后一个数字是,判断此时的和是否刚好为target,如果是,则ans++。
这种思想还犯了一个致命的错误:全局变量保存在堆里面,没有被销毁,临时变量保存在栈里面,会被销毁。
1 | // |
递归的正确写法但是超时了,显然递归的时间开销太大了。得改用其他思路。
1 | // |
后来仔细一想,这个问题其实就是背包的变种,在背包问题中,状态转移的条件是:选或不选,在这个问题则是,加还是减。
定义状态:dp[i][j]: 数组前i个数字组成的表达式之和为j的可能性种数。
这样状态转移方程为: dp[i][j] = dp[i-1][ j+nums[i] ] + dp[i-1][ j-nums[i] ]
虽然思路是对的,但是实现起来还是有很多需要注意的细节,比如刚实现dp在本地是对的,但是提交到远程就错了。
1 | // |
错的主要原因就是dp的第二维:j - nums[i],有可能是小于0的数。这样递推起来肯定就有问题了。所以咋办呢?
其实上述代码的主要思路是这样的:第i行第j列是由第i-1行的两个具体列(j-nums[i],j+nums[i])来决定的,这样就在找j-nums[i]列时可能就找不到了。
不妨换个角度思考,既然第i行由第i-1行来决定,那么是不是也意味着,第i行可以决定第i+1行的数据呢?也就是说,对第i行中每个不为0的数,都会被加到下一行(i+1)的j+nums[i+1] 和j-nums[i+1]这两列中去。因为题意说sum之和不会超过1000,并且我们把列数都进行了平移(第二维度的坐标从-sum -> +sum),所以就能保证j-nums[i+1]一定不会小于0啦~
具体可以看图示:
最后终于AC了的代码:
1 | // |