给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 +
和 -
。对于数组中的任意一个整数,你都可以从 +
或 -
中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例 1:
输入: 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位整数。
几个星期前做这题的时候,我确信只能用搜索来解决。
因为这道题它要把数组所有元素都用上,
因为这题我当时确实想不到怎么拆分成一个个小问题
dp[n]代表目标和为n的方案个数?
算出【1,1,1,1,1】目标和为n-1的方案个数,和算出目标和为n的方案个数有什么关系吗?
后来在动态规划又看见它,出于好奇我看了看别人是怎么做的
他们没有直接开始做,而是把问题转化一下,使得动态规划成为可能
1、转化为dp问题:
设数组为s(num),记和为num。
记目标和为tar
由题目我们可以知道,有的元素前加正号,有的加负号,这些元素加上符号后总和为目标值tar
把所有加正号的元素归入数组s(正),其和为p
把所有加负号的元素归入数组s(负),注意我们还没加负号,s(负)里面的元素还是正的。其和为p
我们可以知道:
①s(正)+s(负)=s(num),所以 p+q=num
②s(正)-s(负)=tar,所以 p-q=tar
③综合:2p=tar+num(p为整数,所以tar+num必须为偶数!)
tar和num已知,只要求出s(num)里面有多少个子集的和为p,就有多少种方案
绕来绕去,好像又绕回求目标和了,好像只是从求tar变成了求p而已,好像还是没有什么变化啊。
但是!求tar要把数组元素都用个遍,求p只要从数组里选几个元素就行了,不一定都用一遍
2、动态规划求解
题目转化为:给定一个正整数数组 nums。从中选取若干元素(不重复),使得元素和为p。求选取元素的方法和
思路:
①总思路
dp[n]代表凑出n的方案数量。
给nums[0],看看能凑出什么数,再给nums[1]看看。。。
最后把nums数组里面的元素都给出去完,题目也就解出来了。
②如何表示凑数字?
设c(m)是已知凑出数字m的方法,n是nums里面的元素
那么,c(m)=c(m-n)+n
注意,如果c(m)=c(m-n)+c(n)可能会导致重复利用元素:
比如数组[1,2,5]
c(3)=1+2,c(2)=2
如果说c(5)=c(3)+c(2),那么很明显,数组里面的元素2被用了两次
③如何知道能凑出什么数字?
假设dp[N],nums元素n,
由②我们知道c(m)=c(m-n)+n,且c(m-n)方案个数已知
那么很明显,在加上n的情况下,m有dp[m-n]种凑法,即dp[m]+=dp[m-n]
现在只探索了怎么凑出m,但是实际上一定就能凑出m?一定只有m被凑出来?
数字 n~N ,都有可能被凑出来。我们需要遍历dp[n]~dp[N]
④如何遍历?
接上③
不就是从dp[n]遍历到dp[N]嘛,有什么可说的呢?
我当时也是这么想的,结果出了大错,计算重复了!
为什么?从因为c(N)很可能由c(n)凑出的!
举个栗子:
数组[1,2,3,4,5]
s(5)=s(1)+4,s(9)=s(5)+4
发现了什么?元素4被用了两次!
即s(9)=s(5)+4这种情况,包含4+4+s(1)这种,且这种情况是不对的,因为4被用了两次
说到这里,算法的基本思路都说完了
3、例题
数组nums[1,2,3,4,5],从里面不重复,随机挑选若干元素,使得和为9
(这题我在https://blog.youkuaiyun.com/mine_song/article/details/70216562?utm_source=copy上看的,选择自己分析一遍)
定义dp[10](即0~9),dp[n]为凑出n的方案个数,c(n)为已知凑出 n 的方案
dp[n]初始为0
①dp[0]:0无论如何都凑得出,记为1;
②1:当只有1时,
dp[9-1] ,dp[8-1] 。。。。 dp[1-1] 都是0,不写了
dp[1]+=dp[1-1],dp[1]=1;
dp=[1,1,0,0,0,0,0,0,0,0]
③1,2:元素1已经在上面用过了,所以只看元素2:
同上,由于dp[9-2] , dp[8-2] 。。。 dp[4-2] 都是0,所以我不写
dp[3]+=dp[3-2],dp[3]=1;
dp[2]+=dp[2-1],dp[2]=1;
dp=[1,1,1,1,0,0,0,0,0,0]
③1,2,3:元素1,2已经用过,看元素3:
dp[9-3] , dp[8-3] , dp[7-3] 都是0,不写
dp[5]+=dp[5-3],dp[5]=1;
dp[4]+=dp[4-3],dp[4]=1;
dp[3]+=dp[3-3],dp[3]=2;
dp=[1,1,2,1,1,1,0,0,0,0]
据此一路分析下去就把答案解出来了
代码:
class Solution {
public int findTargetSumWays(int[] nums, int s) {
int sum = 0;
for(int i=0;i<nums.length;i++)
sum+=nums[i];
if(s>sum||(sum+s)%2!=0)
return 0;
int t=(sum+s)/2;
int[] dp=new int[t+1];
dp[0]=1;
for(int i=0;i<nums.length;i++){
for(int j=t;j>=nums[i];j--){
dp[j]+=dp[j-nums[i]];
}
}
return dp[t];
}
}