leetcode-目标和

122 阅读2分钟

「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战」。

题目

给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:
输入: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

示例 2:
输入:nums = [1], target = 1
输出:1

思路

背包的题目,不过这里要求的,不是最多能装多少,而是刚好装满的方案数。
首先,nums的数值大于等于0,题意就是要把nums分成2个集合,一个集合的数前面放“+”,和记为sump,另一个集合的数前面放“-”,和记为sumn,把整个数组的和记为sum,那么我们可以列出如下方程

sump + sumn = sum (1)
sump - sumn = target (2)

(1)+(2),可得:

2 * sump = sum + target

(1)-(2),可得:

2 * sumn = sum - target

因为sump >= 0 && sumn >= 0,所以必须符合

-sum <= target <= sum

上述的方程才可能有解。
另外一个比较同意忽略掉的条件是 sum + target 和 sum - target必须都是偶数。
满足上述条件的情况下,我们得到sump后,剩下的任务,就是从nums从挑出若干个数,使得和等于sump,求总共有几种方案。(PS:当然,挑出若干个数,使得和等于sumn,也是一样的)
这样,我们就把这个问题转换到了一个01背包,求刚好装满背包有几种方案的问题了,使用01背包的方案来求解。定义状态二维数组dp,dp[i][j]的含义是,从下标为0~i的数字中挑选,满足和为j的方案数。初始条件是dp[i][0] = 1,因为要让和为0,就是不挑任意一个数。状态转移方程可以考虑2种情况

  • nums[i] < j,此时不能选择nums[i],不然就超了,所以dp[i][j] = dp[i-1][j]
  • nums[i] >= j,此时可以不选择num[i],方案数为dp[i-1][j],也可以选择num[i],方案数为dp[i-1][j-nums[i]],所以 dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]] 综合起来,状态转移方程如下:
dp[i][j] = dp[i-1][j]    nums[i] < j
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]    nums[i] >= j

而我们注意到,dp[i][j]只跟dp[i-1][*]有关,所以状态我们可以压缩成一个一维数组。

Java版本代码

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = Arrays.stream(nums).sum();
        if (target > sum || target < -sum || (sum+target)%2==1) {
            return 0;
        }
        int sump = (sum + target) / 2;
        int[] dp = new int[sump+1];
        dp[0] = 1;
        for (int i = 0; i < nums.length; i++) {
            for (int j = sump; j >= 0; j--) {
                if (j >= nums[i]) {
                    dp[j] += dp[j-nums[i]];
                }
            }
        }
        return dp[sump];
    }
}