动态规划 - 背包问题 - 完全背包

完全背包物品数量无限制,可以使用多次的实现方式:背包正序遍历

0-1背包:先物品后背包,物品正序、背包倒序(保证每个物品只用一次)

完全背包:先后顺序都可以,物品正序、背包正序

如果求组合数就是先物品,后背包

如果求排列数就是先背包,后物品

518. 零钱兑换 II: 组合数dp += dp[j-x](组合)

中等

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

dp数组定义的大小一般为(背包大小+1)

1、确定dp数组以及下标含义:dp[j]容量为j的背包能装多少种组合的硬币

2、递推公式:组合数--dp += dp[j-x]

3、初始化:dp[0] = 1

4、确定遍历顺序:如果求组合数就是先物品,后背包。如果求排列数就是先背包,后物品。

5、打印dp数组

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

377. 组合总和 Ⅳ: 组合数dp += dp[j-x](排列)

中等

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

⚠️题目问的是“组合”,但顺序不同就是不同的组合,本质还是排列 :先物品后背包

此时背包的范围由【x, target+1) 变为【1,target+1),所以在推导公式前要先判断 j >=x 

算法思想:抽象为完全背包问题,求容量为target的背包能装多少种排列的硬币

1、确定dp数组以及下标含义:dp[j]容量为j的背包能装多少种排列的元素

2、递推公式:组合数--dp += dp[j-x]

3、初始化:dp[0] = 1

4、确定遍历顺序:如果求组合数就是先物品,后背包。如果求排列数就是先背包,后物品。

5、打印dp数组

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        dp = [0]*(target+1)
        dp[0] = 1
        for j in range(1,target+1): # 排列问题先背包再物品
            for i,x in enumerate(nums):
                # 防止访问超出范围的索引
                if j>=x: 
                    dp[j] += dp[j-x]
        return dp[target]
        

322. 零钱兑换 : 最小物品数 min()

中等

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

算法思想:抽象为完全背包,求容量为amount的背包最少需要多少个硬币能装满。

1、确认dp数组及其下标含义:dp[j] 容量为j的背包最少需要个物品能够装满

2、确定递推公式:dp[j] = min(dp[j], dp[j-x]+1) :凑足总额为j - x的最少个数为dp[j - x],那么只需要加上一个钱币coins[i]即dp[j - x] + 1就是dp[j]

3、初始化:dp[0] = 0,其他下标的dp[j]必须初始化为一个最大的数,不然结果会被初始值覆盖

4、打印

⚠️:其他下标的dp[j]必须初始化为一个最大的数,不然结果会被初始值覆盖

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [inf]*(amount+1)
        dp[0] = 0
        for i,x in enumerate(coins):
            for j in range(x, amount+1):
                dp[j] = min(dp[j], dp[j-x]+1)
        return dp[amount] if dp[amount] != inf else -1

279. 完全平方数: 最小物品数 min()

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

算法思想:抽象为完全背包,求容量为n的背包最少需要多少个完全平方数能装满。

1、确认dp数组及其下标含义:dp[j] 容量为j的背包最少需要个物品能够装满

2、确定递推公式:dp[j] = min(dp[j], dp[j-x]+1) :凑足总额为j - x的最少个数为dp[j - x],那么只需要加上一个钱币coins[i]即dp[j - x] + 1就是dp[j]

3、初始化:dp[0] = 0,其他下标的dp[j]必须初始化为一个最大的数,不然结果会被初始值覆盖

4、打印

⚠️:其他下标的dp[j]必须初始化为一个最大的数,不然结果会被初始值覆盖 

我自己的方法:

class Solution:
    def numSquares(self, n: int) -> int:
        # 求完全平方数的列表
        nums = []
        boundary = int(sqrt(n)) # 边界
        for i in range(1,boundary+1):
            num = i**2 # 求完全平方数
            nums.append(num)

        # 完全背包:动态规划求最小物品数
        dp = [inf]*(n+1) # 其他下表初始为最大数
        dp[0] = 0
        for i,x in enumerate(nums):
            for j in range(x,n+1):
                dp[j] = min(dp[j-x]+1,dp[j]) # 最小物品数的推导公式
        return dp[n]

优化:

class Solution:
    def numSquares(self, n: int) -> int:
        dp = [float('inf')] * (n + 1)
        dp[0] = 0

        for i in range(1, int(n ** 0.5) + 1):  # 遍历物品
            for j in range(i * i, n + 1):  # 遍历背包
                # 更新凑成数字 j 所需的最少完全平方数数量
                dp[j] = min(dp[j - i * i] + 1, dp[j])

        return dp[n]

139. 单词拆分

中等

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。

拆分时可以重复使用字典中的单词,说明就是一个完全背包!

算法思想:抽象为完全背包背题,求是否能够装满背包

1、确认dp数组及其下标含义:dp[j] 长度为j的字符串,dp[j]为true,表示可以拆分为一个或多个在字典中出现的单词。

2、确定递推公式:如果确定dp[i] 是true,且 [i, j] 这个区间的子串出现在字典里,那么dp[j]一定是true。(i < j )。

if dp[i] and s[i:j] in wordSet:  # 检查是否可以分割
    dp[j] = True

3、初始化:dp[0] = true

4、打印

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        wordSet = set(wordDict)  # 使用集合提高查找效率
        dp = [False] * (len(s) + 1)
        dp[0] = True  # 空字符串可以被分割
        
        for j in range(1, len(s) + 1):
            for i in range(j):  # i 从 0 到 j-1
                if dp[i] and s[i:j] in wordSet:  # 检查是否可以分割
                    dp[j] = True
                    break  # 找到一个分割后可以提前结束
            
        return dp[len(s)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值