时间复杂度:O(n²),空间复杂度:O(n)
解题思路
动态规划之01背包。
首先我们先导一下公式。令正数和为sumP,负数和为sumN,数组元素总和为sum,由题意可知
- sumP+sumN=target
- sumP-sumN=sum
所以①+②可得2*sumP=target+sum即sumP=(target+sum)/2
这样我们就可以把该题转化为01背包,在数组中选几个元素凑成(target+sum)/2的和,和《416.分割等和子集》题思路相仿,不过该题dp[i][j]的含义为前i个元素的和为j的方案数。
这样当我们遍历数组时对于每个元素都有两种情况——选择它成为正数和不选择它让它当负数,需要注意的是,如果元素num>j,那么我们不能选择它成为正数,因为如果选择它会使前i个元素的和超过j,不满足题意;而对于num<j的元素,如果我们选择它,那么dp[i][j]=dp[i-1][j-nums[i]],相当于选择它的方案数就等同于前i-1个元素凑出和为j-nums[i]的方案数,如果是不选择它,dp[i][j]=dp[i-1][j],方案数就是前i-1个元素和为j的方案数,最后dp[i][j]就是这两种情况方案数的总和。
综上,状态转移方程为
- dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i]] (j≥nums[i])
- dp[i][j]=dp[i-1][j](j<nums[i])
我们可以发现dp[i][j]的状态均依赖于dp[i-1]的状态,所以我们可以用滚动数组的方法将二维数组压缩成一维数组 ,空间复杂度降为O(n)。但是,接下来在更新dp时我们必须从大到小更新,这样不会造成我们依赖的状态已经被修改的情况发生。
这样状态转移方程就变成了
- dp[j]=dp[j]+dp[j-nums[i]] (j≥nums[i])
- dp[j]=dp[j](j<nums[i])
AC代码
func findTargetSumWays(nums []int, target int) int {
//正数和为(target+数组总和)/2
s:=0
for _,num:=range nums{
s+=num
}
if s<target||s+target<0||(s+target)%2==1{
return 0
}
sumP:=(s+target)/2
dp:=make([]int,sumP+1)
dp[0]=1
for _,num:=range nums{
for j:=sumP;j>=num;j--{
dp[j]+=dp[j-num]
}
}
return dp[sumP]
}
感悟
这次终于可以正确想到动态规划的思路了,主要原因是昨天做的题目就是“分割等和子集”题,对01背包的记忆更深刻一些。