• 算法训练营day45|动态规划 part07:完全背包 (LeetCode 70. 爬楼梯(进阶)、322. 零钱兑换、279.完全平方数)


    70. 爬楼梯(进阶)(求排列方法数)

    题目链接🔥
    假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
    每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
    注意:给定 n 是一个正整数。

    示例 1: 输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。
    1 阶 + 1 阶
    2 阶

    示例 2: 输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。
    1 阶 + 1 阶 + 1 阶
    1 阶 + 2 阶
    2 阶 + 1 阶

    思路分析

    之前讲这道题目的时候,因为还没有讲背包问题,所以就只是讲了一下爬楼梯最直接的动规方法(斐波那契)。这次再用背包做一遍。
    这就是一个完全背包的排列问题。

    1. 确定dp数组以及下标的含义

    dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法。

    1. 确定递推公式

    求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];

    本题呢,dp[i]有几种来源,dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j]

    那么递推公式为:dp[i] += dp[i - j]

    1. dp数组如何初始化

    既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0了。

    下标非0的dp[i]初始化为0,因为dp[i]是靠dp[i-j]累计上来的,dp[i]本身为0这样才不会影响结果

    1. 确定遍历顺序

    这是背包里求排列问题,即:1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样!

    所以需将target放在外循环,将nums放在内循环。

    每一步可以走多次,这是完全背包,内循环需要从前向后遍历。

    1. 举例来推导dp数组

    代码实现

    class Solution {
    public:
        int climbStairs(int n) {
            vector<int> dp(n + 1, 0);
            dp[0] = 1;
            for (int i = 1; i <= n; i++) { // 遍历背包
                for (int j = 1; j <= m; j++) { // 遍历物品
                    if (i - j >= 0) dp[i] += dp[i - j];
                }
            }
            return dp[n];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    代码中m表示最多可以爬m个台阶,代码中把m改成2就是本题可以AC的代码了。


    322. 零钱兑换(求等于背包重量的最小物品数)

    题目链接🔥🔥
    给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
    你可以认为每种硬币的数量是无限的。

    示例 1:
    输入:coins = [1, 2, 5], amount = 11
    输出:3
    解释:11 = 5 + 5 + 1

    示例 2:
    输入:coins = [2], amount = 3
    输出:-1
    示例 3:

    输入:coins = [1], amount = 0
    输出:0

    示例 4:
    输入:coins = [1], amount = 1
    输出:1

    示例 5:
    输入:coins = [1], amount = 2
    输出:2

    提示:
    1 <= coins.length <= 12
    1 <= coins[i] <= 2^31 - 1
    0 <= amount <= 10^4

    思路分析

    动规五部曲分析如下:

    1. 确定dp数组以及下标的含义

    dp[j]:凑足总额为j所需钱币的最少个数为dp[j]

    1. 确定递推公式

    凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i])

    所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。

    递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);

    1. dp数组如何初始化

    首先凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;

    其他下标对应的数值呢?

    考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。

    所以下标非0的元素都是应该是最大值。

    1. 确定遍历顺序

    本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数。

    所以本题并不强调集合是组合还是排列。

    如果求组合数就是外层for循环遍历物品,内层for遍历背包。
    如果求排列数就是外层for遍历背包,内层for循环遍历物品。

    所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的!

    1. 举例推导dp数组

    以输入:coins = [1, 2, 5], amount = 5为例

    在这里插入图片描述

    代码实现

    class Solution {
    public:
        int coinChange(vector<int>& coins, int amount) {
            vector<int> dp(amount+1,INT_MAX-1);
            dp[0]=0;
            for(int i=0;i<coins.size();i++){
                for(int j=coins[i];j<=amount;j++){
                    dp[j]=min(dp[j],dp[j-coins[i]]+1);
                }
            }
            if(dp[amount]!=INT_MAX-1) return dp[amount];
            else return -1;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    思考总结

    本题是要求最少硬币数量,硬币是组合数还是排列数都无所谓!所以两个for循环先后顺序怎样都可以!


    279.完全平方数 (求等于背包重量的最小物品数)

    题目链接🔥🔥
    给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
    给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
    完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

    示例 1:
    输入:n = 12
    输出:3
    解释:12 = 4 + 4 + 4

    示例 2:
    输入:n = 13
    输出:2
    解释:13 = 4 + 9

    提示:
    1 <= n <= 10^4

    思路分析

    完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品?

    跟上道题其实一模一样,直接看代码吧

    代码实现

    class Solution {
    public:
        int numSquares(int n) {
            vector<int> dp(n+1,INT_MAX);
            dp[0]=0;
            for(int i=1;i*i<=n;i++){
                for(int j=i*i;j<=n;j++){
                    dp[j]=min(dp[j],dp[j-i*i]+1);
                }
            }
            return dp[n];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这里dp初始化成INT_MAX而不用担心dp[j-i*i]+1越界,因为物品里面有1,不管背包容量是多少,一定可以凑成。


  • 相关阅读:
    Linux之bind 函数(详细篇)
    前端Vue页面中如何展示本地图片
    【云原生进阶之PaaS中间件】第一章Redis-1.5.1安装配置
    C++【类和对象】【三】
    新能源汽车小米su7
    图片铺满div元素不变形,超出部分隐藏,保留中心部分css代码
    安科瑞基于物联网技术的基站能耗监控解决方案-Susie 周
    隆云通二氧化硫传感器
    SpringCloud-06-Config
    计算机与软件技术系毕业设计(论文)实施意见-计算机毕业设计论文怎么写
  • 原文地址:https://blog.csdn.net/weixin_43399263/article/details/132756921