题目:
给你一个整数数组和一个目标值,对数组中的所有数字可以使用+或-使其和为目标值,问有多少种方式。
这道题之前用了回溯的方式来做,结果运行了1秒多,效率十分低下。这次借鉴了别人01背包的做法,感觉十分巧妙。
解法:
对于数组中的数字,可以将其分为两部分,一部分为使用+的数字,和为sumA,另一部分为使用了-的数字,和为sumB,根据题意可以得到如下的式子:
(1)sumA + sumB = sum
(2)sumA - sumB = target
将上述两个式子相加得到:
2sumA = sum + target,也就是sumA = (sum + target) / 2
那么本题就可以转为:
有一些物品,第i个物品的重量为nums[i],现有一个背包的容量为sumA,问有多少种方式可以将背包恰好填满。(01背包的本质就是第i个物品取或者不取)
需要注意的是nums数组都是非负整数,所以sumA、sumB、sum必须是非负整数,并且易知2sumA是偶数,所以sum + target必须是偶数,否则没有任何方式可以凑成目标和。
1. 确定dp数组
dp[i][j]表示前i个物品有多少种方式可以恰好装入容量为j的背包。
2. 转态转移方程
当背包容量大于等于第i个物品的重量,那么可以装入该物品,此时背包的结果为
dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j]
否则当前背包的结果只能来前i-1个物品
dp[i][j] = dp[i-1][j]
3. 初始化dp数组
dp[i][0] 表示背包容量为0的情况下所有数字都不选,因此初始化为1
4. 确定遍历顺序
先遍历数字再遍历容量。数组从第一个数字开始,背包的容量可以为0,故需要从0开始遍历。
func findTargetSumWays(nums []int, target int) int {
n := len(nums)
dp := make([][]int, n+1)
sum :=0
for i:=0; i< n;i++{
sum += nums[i]
}
bagSize := (sum + target) / 2
//当target小于0时容量可能为0
if bagSize < 0{
bagSize = -bagSize
}
if (sum + target) %2 == 1{
return 0
}
for i:=0; i<=n;i++{
dp[i] = make([]int,bagSize+1)
dp[i][0] = 1
}
for i:=1; i<= n;i++{
for j:=0; j<=bagSize;j++{
//这里与最外层从1开始遍历对应
if j>=nums[i-1] {
dp[i][j] = dp[i-1][j-nums[i-1]] + dp[i-1][j]
}else{
dp[i][j] = dp[i-1][j]
}
}
}
return dp[n][bagSize]
}