• LeetCode刷题复盘笔记—一文搞懂0 - 1背包之494. 目标和问题(动态规划系列第九篇)


    今日主要总结一下动态规划0-1背包的一道题目,494. 目标和问题

    题目:494. 目标和

    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

    本题重难点

    这道题目咋眼一看和动态规划背包啥的也没啥关系。

    大家在做这道题之前最好看一下一文搞懂纯0-1背包问题我对0-1背包的基础理论知识有详细的讲解,本道题其实就是0-1 背包问题的应用,而这道题的难点主要在于怎么把494. 目标和问题抽象成一个0-1 背包问题。

    本题要如何使表达式结果为target呢?

    假设整数前添加 '+'号的总和为x,那么整数前添加 '-'号对应的总和就是sum - x。

    所以我们要求的是 x - (sum - x) = target

    x = (target + sum) / 2

    此时问题就转化为,装满容量为x背包,有几种方法。**

    这里的x,就是bagSize,也就是我们后面要求的背包容量。

    大家看到(target + sum) / 2 应该担心计算的过程中向下取整有没有影响。

    这么担心就对了,例如sum 是5,target是2的话其实就是无解的,所以:

    当(target + sum) 不能整除2或者 target > sum时,都是没有无解的。

    那么我们再回归到01背包问题,为什么是01背包呢?

    因为每个物品(题目中的1)只用一次!

    这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。

    本题则是装满有几种方法。其实这就是一个组合问题了。

    1. 确定dp数组以及下标的含义
      dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
    2. 确定递推公式
      有哪些来源可以推出dp[j]呢?
      只要搞到nums[i]),凑成dp[j]就有dp[j - nums[i]] 种方法。

    例如:dp[j],j 为5,

    已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
    已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
    已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
    已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
    已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包
    那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。
    所以求组合类问题的公式,都是类似这种:

    dp[j] += dp[j - nums[i]]
    
    • 1

    这个公式在后面在讲解背包解决排列组合问题的时候还会用到!

    1. dp数组如何初始化
      从递归公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递归结果将都是0。
      这里有录友可能认为从dp数组定义来说 dp[0] 应该是0,也有录友认为dp[0]应该是1。
      其实不要硬去解释它的含义,咱就把 dp[0]的情况带入本题看看就是应该等于多少。
      如果数组[0] ,target = 0,那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1, 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。
      所以本题我们应该初始化 dp[0] 为 1。
      可能有同学想了,那 如果是 数组[0,0,0,0,0] target = 0 呢。
      其实 此时最终的dp[0] = 32,也就是这五个零 子集的所有组合情况,但此dp[0]非彼dp[0],dp[0]能算出32,其基础是因为dp[0] = 1 累加起来的。
      dp[j]其他下标对应的数值也应该初始化为0,从递归公式也可以看出,dp[j]要保证是0的初始值,才能正确的由dp[j - nums[i]]推导出来。

    2. 确定遍历顺序
      我们在一文搞懂纯0-1背包问题讲过对于01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。

    3. 举例推导dp数组

    输入:nums: [1, 1, 1, 1, 1], target: 3

    bagSize = (target + sum) / 2 = (3 + 5) / 2 = 4

    dp数组状态变化如下:

    在这里插入图片描述

    C++代码

    class Solution {
    public:
        int findTargetSumWays(vector<int>& nums, int target) {
            int sum = 0;
            for(auto& n : nums) sum += n;
            if(abs(target) > sum) return 0;
            if((target + sum) % 2 == 1) return 0;
            int bagSize = (target + sum) / 2;
            vector<int> dp(bagSize + 1, 0);
            dp[0] = 1;
            for(int i = 0; i < nums.size(); i++){
                for(int j = bagSize; j >= nums[i]; j--){
                    dp[j] += dp[j - nums[i]];
                }
            }
            return dp[bagSize];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    总结

    动态规划
    英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
    动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的

    对于动态规划问题,可以拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!

    1. 确定dp数组(dp table)以及下标的含义
    2. 确定递推公式
    3. dp数组如何初始化
    4. 确定遍历顺序
    5. 举例推导dp数组

    这篇文章主要总结了一些动态规划解决494. 目标和问题,依然是使用动规五部曲,做每道动态规划题目这五步都要弄清楚才能更清楚的理解题目!

    本题还是有点难度,大家也可以记住,在求装满背包有几种方法的情况下,递推公式一般为:

    dp[j] += dp[j - nums[i]];
    
    • 1

    后面我们在讲解完全背包的时候,还会用到这个递推公式!

    欢迎大家关注本人公众号:编程复盘与思考随笔

    (关注后可以免费获得本人在csdn发布的资源源码)

  • 相关阅读:
    AttributeError: cannot assign module before Module.__init__() call
    图神经网络--pytorch_geometric基本使用
    连续10年霸榜第一 程序员「最常用」的编程语言是。。。。
    【C++】类和对象(下)
    Matlab提取excel数据及处理的实操举例
    QT 智能指针 QPointer QScopedPointer QSharedPointer QWeakPointer QSharedDataPointer 隐式共享 显示共享
    37、引擎高可用方案
    沃尔玛Walmart EDI 850订单详解
    炫酷的宇宙空间
    html5+css3
  • 原文地址:https://blog.csdn.net/qq_43498345/article/details/128167862