当前位置: 首页 > article >正文

目标和(DP)

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

DP:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int total = 0;
        for(int n : nums){
            total += n;
        }
        if((total + target) % 2 != 0 || total < abs(target)){
            return 0;
        }
        int sub = (total + target) / 2;
        vector<int> dp(sub + 1, 0);
        dp[0] = 1;
        for(int n : nums){
            for(int j = sub; j >= n; j--){
                dp[j] += dp[j - n];
            }
        }
        return dp[sub];
    }


};

(total + target) % 2 != 0

如果 total + target 是奇数,那么无法将它分割成两部分,返回 0


nums 划分为两个子集:一个子集 P 代表带正号的部分。另一个子集 N 代表带负号的部分。

根据题目要求,这两个子集满足:

  • P − N=target
  • P + N = sum(nums)

通过这两个方程,我们可以推导出 P 的值:

  • P =(sum(nums) + target) / 2

为了确保 P 是一个整数,(sum(nums) + target) 必须是偶数。如果 (sum(nums) + target) 是奇数,那么 P 就会是一个小数,在这种情况下,问题无解,直接返回 0

total < abs(target)

如果 total 小于 target 的绝对值,则无法通过 +- 来组合出 target,所以直接返回 0

这就变成了一个子集和问题:计算所有可以构成和为 sub 的子集数目。

定义dp[i] 表示可以组成和为 i 的子集数目。

初始时,dp[0] = 1,表示可以通过一个空集来组成和为 0 的子集。所有其他 dp[i](对于 i > 0)都为 0,因为还没有计算任何子集。

为什么从 sub 开始倒序遍历?

  • 为了避免当前的元素 n 被重复使用。假设我们正在处理元素 n 并且当前正在更新 dp[j],如果我们从 dp[sub] 开始遍历并往下计算,就可以确保每次计算时,dp[j - n] 只会使用上一轮的值。
  • 如果我们从小到大遍历,可能会在同一轮循环中多次使用同一个元素 n,这会导致错误的计算。

dp[j] += dp[j - n] 的含义:

  • dp[j - n] 代表当前元素 n 加入之前,可以组成和为 j - n 的子集数量。
  • 加上 n 后,和变为 j,因此可以通过将 dp[j - n] 加到 dp[j] 上,来表示有多少个子集和为 j
  • 换句话说,dp[j] 就是之前可以组成和为 j - n 的子集数目与当前元素 n 组合后组成和为 j 的子集数目的和。

举个例子:

nums = [1, 2, 3], sub = 4,那么 dp 数组初始化为:

dp = [1, 0, 0, 0, 0]  

第一次循环,n = 1

dp[4] += dp[4 - 1] = dp[3]dp[4] = 0

dp[3] += dp[3 - 1] = dp[2]dp[3] = 0

dp[2] += dp[2 - 1] = dp[1]dp[2] = 0

dp[1] += dp[1 - 1] = dp[0]dp[1] = 1

第二次循环,n = 2

dp[4] += dp[4 - 2] = dp[2]dp[4] = 0

dp[3] += dp[3 - 2] = dp[1]dp[3] = 1

dp[2] += dp[2 - 2] = dp[0]dp[2] = 1

第三次循环,n = 3

dp[4] += dp[4 - 3] = dp[1]dp[4] = 1

dp[3] += dp[3 - 3] = dp[0]dp[3] = 2

最后:

dp = [1, 1, 1, 2, 1] ,    dp[sub] = dp[4] = 1

DFS:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        return dfs(nums, target, 0, 0);
    }

    int dfs(vector<int>& nums, int target, int i, int sum){
        if(i == nums.size()){
            return sum == target ? 1 : 0;
        }
        int a = dfs(nums, target, i + 1, sum + nums[i]);
        int b = dfs(nums, target, i + 1, sum - nums[i]);
        return a + b;
    }
};


http://www.kler.cn/a/389422.html

相关文章:

  • Python提取PDF和DOCX中的文本、图片和表格
  • C#发票识别、发票查验接口集成、电子发票(航空运输电子行程单)
  • 【图像压缩感知】论文阅读:Self-supervised Scalable Deep Compressed Sensing
  • Spring框架之观察者模式 (Observer Pattern)
  • 事件循环 -- 资源总结(浏览器进程模型、事件循环机制、练习题)
  • C++单例模式实现
  • Qt中 QWidget 和 QMainWindow 区别
  • YoloV8改进策略:注意力改进|EPSANet,卷积神经网络上的高效金字塔挤压注意力块|即插即用|代码+改进方法
  • python+pptx:(二)添加图片、表格、形状、模版渲染
  • 信息安全数学基础(48)椭圆曲线
  • 工具收集 - 进程资源管理器、进程监视器
  • Kafka高频面试题详解
  • 010_SSH_Sqlserver多媒体技术与应用课程网(学习资料+前台考试)_lwplus87
  • 满200减30,怎么样用python计算凑单正好满足要求呢?
  • 【flask开启进程,前端内容图片化并转pdf-会议签到补充】
  • 如何看待鸿蒙生态
  • Pr:视频过渡快速参考(合集 · 2025版)
  • LeetCode 第 423 场周赛个人题解
  • 【数据中心技术
  • 响应拦截器的判断
  • pytorch register_buffer介绍
  • SIwave:释放 SIwizard 求解器的强大功能
  • 二叉树(C 语言)
  • 关于 spring boot - application.yml 加载顺序
  • LabVIEW实验室液压制动系统
  • L2 级智能驾驶车辆随时间变化的HMI系统提示效果研究