夯实算法-32.目标和

116 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情

题目:LeetCode

给你一个整数数组 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

提示:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums[i]) <= 1000
  • -1000 <= target <= 1000

解题思路

  • 分析题目可以转换成01背包问题:把 - 当成 0,不选;把 + 当成 1,选。
  • 原本(-nums) + (+nums) == target,表达式左边和右边都加上 sum,就转换成 0 + 2 * (+nums) == sum + target,为方便,可以再进行除2操作,变成 +nums == (sum + target) / 2
  • 由此可推出,如果(sum + target) 为奇数,说明不存在对应的 +nums 序列,即不可取
  • 接下来就是正常的 01背包 动态规划问题
  • 滚动数组:逆序,现在没有减法的情况下,可以保证无后效性

代码实现

public int findTargetSumWays(int[] nums, int target) {
    int sum = 0;
    for(int temp : nums) {
        sum += temp;
    }
    // 不在范围的情况 && 奇数无法匹配到选取方式(可证)
    if(sum < target || (sum + target) % 2 == 1) {
        return 0;
    }

    // 转换成 01背包:-号转成不取;+号转成取,两倍
    // 实际上,只要考虑到 target + sum 即可,后面的和结果无关
    int top = (sum + target) / 2 + 1;

    // dp[i][j]:下标[0 ~ i]构成的数集,能得到 j - sum 的情况种数
    int[] dp = new int[top];
    // 初始化:第一个元素不取,只能给 0 带来 1 个种数
    dp[0] = 1;

    // 状态转移
    for(int i = 0; i < nums.length; i++) {
        for(int j = top - 1; j >= nums[i]; j--) {
            // Case 1:取第 i 个元素
            dp[j] += dp[j - nums[i]];
            // Case 2: 不取第 i 个元素(一维情况下相当于不用考虑)
        }
    }
    return dp[top - 1];
}

运行结果

微信截图_20221013235106.png

复杂度分析

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)

掘金(JUEJIN)  一起分享知识, Keep Learning!