LeetCode 494 目标和

有点难

Posted by Pacras on March 11, 2019

参考coordinate_blog的博客

题目描述

        给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

注意:

  1. 数组长度不会超过20,并且数组中的值全为正数;
  2. 初始数组的和不会超过1000;
  3. 保证返回的结果为32位整数。

解1

        每个数字前都可以加正负号,那岂不是有 2^len(nums) 种可能,想到用动态规划来做,但和之前做的动态规划问题有不同,没能找到推导公式。

        这篇博客 给出的解题思路很厉害:我们添加符号的过程事实上就把整个数组分为了两部分,正数部分 P, 负数部分 N,要满足 sum(P) + sum(N) == S ,同时 sum(P) - sum(N) == sum(nums)  。那么可得 sum(P) = (sum(nums) + S) // 2,把这个值记为 target 。这里注意 sum(nums) 不能小于 S,且两者之和必须是偶数。这样一来问题就转化为找到和为 target 的子序列个数。

        新问题可以用动态规划来做。dp[i] 表示和为 i 的子序列个数,初始化 dp[0] 为1,啥都没有嘛可不就一种情况。

dp = [0] * (target+1)
dp[0] = 1
for num in nums:
   for i in range(target, num-1, -1):
        dp[i] += dp[i-num]
return dp[target]

解2

        还是用动态规划来做,本题可类比背包问题,此时的背包容量是 [-sum(nums), sum(nums)],有 len(nums) 个物体,装容量恰为 S 的方法数量。用二维数组 dp[len(nums)+1][(2*sum(nums)+1] 存储状态。dp[i][j] 表示到第 i 个物体时拾取容量为 j 的方法数。递推公式为:dp[i][j] += dp[i-1][j-nums[i-1]]  dp[i][j] += dp[i-1][j+nums[i-1]]。代码如下:

for i in range(1, len(nums)+1):
   for j in range(-sum_nums, sum_nums+1):
        dp[i][j] += dp[i-1][j-nums[i-1]] if -sum_nums <= j-nums[i-1] <= sum_nums else 0 
        dp[i][j] += dp[i-1][j+nums[i-1]] if -sum_nums <= j+nums[i-1] <= sum_nums else 0 
return dp[len(nums)][S]

总结

        做题的时候感觉还是缺少数学思维,不能简化问题;再就是题目中有数组长度数组和之类的提示,应该联想到怎么构造动态规划数组的。动态规划问题还得再看看,打算学习下背包九讲。ps:这类问题的时空复杂度还是蛮高的。