题目
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 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
解析
原问题等同于: 找到nums一个正子集P和一个负子集N,使得总和等于target。即sum( P) - sum(N) == target(就是减号,因为N是负数)
即 sum( P) + sum(N) + ( sum( P) - sum(N) ) == sum( P) + sum(N) + target;
即 左边等于2 * sum( P) , 右边等于 sum(nums) + target(因为sum( P) + sum(N) = sum(nums))
其中target + sum(nums)必须 >=0且为偶数,否则等式不可能成立(这个不理解也不太重要,先看后面的重点)
则问题转换为:存在多少个子集P,使sum( P) == (target + sum(nums))/2;
动态规划五部曲:
1.确定dp数组以及下标的含义
dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法,注意这里和之前的问题不同,之前的比如是能否填满背包,或者是别的,这里是有多少种方法;
2.确定递推公式
求组合类问题的公式,都是类似这种:dp[j] += dp[j - nums[i]] (背下来)
3.dp数组如何初始化
本题需要初始化dp[0] = 1,表示填满容积为0的背包,有一种方法(不然的话后面全是0了)
4.确定遍历顺序
一维背包的话,外层遍历物品,内层遍历背包且倒序
func findTargetSumWays(nums []int, target int) int {
sum := 0
for _, v := range nums {
sum += v
}
if abs(target) > sum {
return 0
}
if (sum + target) % 2 == 1 {
return 0
}
// 计算背包大小
bag := (sum + target) / 2
// 定义bp数组
dp := make([]int, bag+1) //感觉这种题目,要是实在不知道有多大,可以先初始化一个较大的值
// 初始化dp[j],含义是填满容量为j这么大体积的背包,有dp[j]种方法
dp[0] = 1
// 遍历顺序
for i := 0; i < len(nums); i++ { // 遍历物品放在外循环,遍历背包再内循环且倒序
for j := bag; j >= nums[i]; j-- {
// 推导公式
dp[j] += dp[j-nums[i]]
}
}
return dp[bag]
}
func abs(x int) int {
return int(math.Abs(float64(x)))
}
再次强调下,本题是填满容量为j的背包,有多少种方法