• 力扣518题、377题、70题(完全背包、排列组合)


    518、零钱问题II

    基本思路:

    钱币数量不限----完全背包问题

    纯完全背包是能否凑成总金额,而本题是要求凑成总金额的方法个数

    具体实现:

    1、确认状态:

    dp[j]:凑成总金额j的货币组合数为dp[j]

    2、状态转移:

    如果不使用coins[i]这个面值的硬币,就继承上面几种硬币的凑法

    dp[i][j] = dp[i-1][j]

    如果使用coins[i]这个面值的硬币,dp[i][j]=上面几种硬币的凑法dp[i-1][j]+新硬币加进来,剩余金额的凑法dp[i][j-coins[i-1]]

    优化:dp[j] += dp[j - coins[i]];

    3、初始状态:

    dp[0] = 1

    从dp[i]的含义上来讲就是,凑成总金额0的货币组合数为1。

    4、遍历顺序:

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

    for (int i = 0; i < coins.size(); i++) { // 遍历物品
        for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
            dp[j] += dp[j - coins[i]];
        }
    }

    假设:coins[0] = 1,coins[1] = 5。那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。

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

    for (int j = 0; j <= amount; j++) { // 遍历背包容量
        for (int i = 0; i < coins.size(); i++) { // 遍历物品
            if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
        }
    }

    背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。

    5、举例

    输入: amount = 5, coins = [1, 2, 5] ,dp状态图如下:

    代码:

    class Solution:
        def change(self, amount: int, coins: List[int]) -> int:
            n = len(coins)
            dp = [[0]*(amount+1) for _ in range(n+1)]
            for i in range(n+1):
                dp[i][0] = 1
            for i in range(1,n+1):
                for j in range(1,amount+1):
                    if j-coins[i-1]>=0:
                        dp[i][j] = dp[i-1][j]+dp[i][j-coins[i-1]]
                    else:
                        dp[i][j] = dp[i-1][j]
            return dp[n][amount]

    状态压缩

    class Solution {
        public int change(int amount, int[] coins) {
            int[] dp = new int[amount + 1];
            dp[0] = 1;
            for (int i = 0; i < coins.length; i++){
                for (int j = coins[i]; j <= amount; j++){
                    dp[j] += dp[j - coins[i]];
                }
            }
            return dp[amount];
        }
    }

    377、组合总和IV

    基本思想:

    数组中的数可以随便取-----完全背包

    顺序不同的序列被视作不同的组合------排列

    具体实现:

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

    dp[i]是凑成目标正整数为i的排列个数

    2、确定递推公式

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

    3、dp数组初始化

    dp[0]要初始化为1,这样递推其他dp[i]的时候才会有数值基础。

    dp[0] = 1 没有意义

    非0下标的dp[i]初始化为0

    4、确定遍历顺序

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

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

    本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。

    5、举例

    代码:

    class Solution {
        public int combinationSum4(int[] nums, int target) {
            int[] dp = new int[target + 1];
            dp[0] = 1;
            for (int i = 0; i <= target; i++){
                for (int j = 0; j < nums.length; j++){
                    if (i >= nums[j]) {
                        dp[i] += dp[i - nums[j]];
                    }
                }
            }
            return dp[target];
        }
    }

    70、爬楼梯进阶版

    原题为每次只可以爬 1 或 2 个台阶。

    改为一步一个台阶,两个台阶,三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?

    基本思想:

    1阶,2阶,.... m阶就是物品,楼顶就是背包。

    每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。-----完全背包

    问跳到楼顶有几种方法其实就是问装满背包有几种方法。

    具体实现:

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

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

    2.递推公式

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

    3.初始化

    dp[0] = 1,其余元素都为0

    4.确定遍历顺序

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

    需将target(容量)放在外循环,将nums(物品)放在内循环。

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

    代码:

    class Solution {
        public int climbStairs(int n) {
            int[] dp = new int[n + 1];
            int[] weight = {1,2};
            dp[0] = 1;
    
            for (int i = 0; i <= n; i++) {
                for (int j = 0; j < weight.length; j++) {
                    if (i >= weight[j]) dp[i] += dp[i - weight[j]];
                }
            }
    
            return dp[n];
        }
    }
  • 相关阅读:
    如果网站文字不让复制怎么办,谷歌浏览器
    Mac 微信双开
    git 线上一不小心拉取代码了,如何恢复
    php 验证港澳台身份证
    把照片弄成50k以内
    Gson序列化时排除字段
    实战Springboot内置Tomcat配置调优
    svg图标爽使用
    laravel的post请求分页数据
    php的isset函数相关问题
  • 原文地址:https://www.cnblogs.com/zhaojiayu/p/15652186.html
Copyright © 2020-2023  润新知