描述
给定一个非负整数数组,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位整数存下。
思路
动态规划 和 背包问题
1. 背包问题转化
此问题可转化为 找两个子集 P + N = All(其中P 为正子集 N 为负子集 All为全集 )
使得sum(P) - sum(N) = S
我们两边加上 sum(P) + sum(N) 即
sum(P) - sum(N) + sum(P) + sum(N) = S + sum(P) + sum(N)
2sum(P) = S + sum(P) + sum(N) 因为sum(P) + sum(N) = sum(All)
那么 sum(P) = (S + sum(All)/2
显然 如果 S + sum(All) 为奇数的话 是不存在答案的
这个问题就变成了 给定一个数组 求和target=(S + sum(All)/2 的子集有多少种 这个问题跟 leetcode 416. 分割等和子集非常类似 ,有兴趣可以点这里
2. 动态规划转移方程
dp[i][j] 表示 前i 个数字 和 = j有多少种方法
如果j >= num 则
dp[i][j] = dp[i-1][j] + dp[i][j - num]
否则
dp[i][j] = dp[i-1][j]
3. dp[0][0] = 1
实现
func findTargetSumWays(nums []int, S int) int {
var l = len(nums)
var sum int
for _, num := range nums{
sum += num
}
if sum < S || (S + sum)%2 == 1{
return 0
}
target := (S + sum)/2
dp := make([][]int, l + 1)
dp[0] = make([]int, target + 1)
dp[0][0] = 1
for i := 1; i <= l; i++{
dp[i] = make([]int, target + 1)
for j := 0; j <= target; j++{
if j >= nums[i-1]{
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]]
}else{
dp[i][j] = dp[i-1][j]
}
}
}
return dp[l][target]
}