day38 动态规划06

71 阅读4分钟

322. 零钱兑换

文章讲解

思路:

完全背包问题,coins是无限的,求最少硬币数量;amount顺序遍历。组合先迭代物品;注意dp[j-coin]=inf的情况
dp[i][j]表示amount为j,然后前i个硬币的最少数量 size是n+1 amount+1
初始化:j=0时候,最少数量为1
递推:dp[i][j] = min(dp[i-1][j],  dp[i][j - coins[i-1]])
递推:dp[j] = min(dp[j],  dp[j - coins[i-1]])

注意:零钱兑换问题,有几种方法初始化0为1,如果是最小数量的化初始化0。

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        n = len(coins)
        # dp = [0]  * (amount + 1) # 注意1:求最小值时候,初始化要初始化最大
        dp = [float("inf")]  * (amount + 1) 

        dp[0] = 0 # 注意2:amount为0时候,不需要硬币凑

        # 迭代硬币和amount
        for coin in coins:
            for j in range(coin, amount+1):
                # 注意3:dp[j-coin]有意义的时候才转移
                if dp[j-coin] != float("inf"):
                    dp[j] = min(dp[j], dp[j-coin]+1) # 注意4:如果取coin硬币,需要+1
        # 注意5:没有答案的情况
        return dp[amount] if dp[amount] != float("inf") else -1

279. 完全平方数

文章讲解

class Solution(object):
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        # 定义:和为 i 的平方数的最小数量是 dp[i]
        dp = [float('inf')] * (n+1) # 计算最小,初始化时候需要最大
        # base case
        dp[0] = 0
        # 状态转移方程
        for j in range(1, n+1):
            for i in range(1, int(n**0.5)+1):
                dp[j] = min(dp[j], dp[j - i*i]+1) # # j-i*i 只要再加一个平方数 i * i 即可凑出 i
        return dp[n]

class Solution(object):
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        # 定义:和为 i 的平方数的最小数量是 dp[i]
        dp = [float('inf')] * (n+1) # 计算最小,初始化时候需要最大
        # base case
        dp[0] = 0
        # 状态转移方程
        for i in range(1, int(n**0.5)+1):
            for j in range(i*i, n+1):
                dp[j] = min(dp[j], dp[j - i*i]+1) # # j-i*i 只要再加一个平方数 i * i 即可凑出 i
        return dp[n]

139. 单词拆分

文章讲解

思路:

dp[i]表示前i个字符s[0,..,i-1]是否能被正确分割
dp[0]=True # 空字符串true
递推:假设存在j [0,...,i]使得dp[j]=true 并且s[j:i]在wordDict里面,那就dp[i]=true

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        n = len(s)
        wordSet = set(wordDict)
        dp = [False] * (n+1)

        dp[0] = True

        for i in range(1, n+1):
            for j in range(i):
                if dp[j] and s[j:i] in wordSet: # s[0,...,j]=true,并且s[j:i]是字典中子串
                    dp[i] = True
                    break  # 找到一个有效的分割,退出内层循环
        return dp[n]

多重背包

文章讲解

题目描述

你是一名宇航员,即将前往一个遥远的行星。在这个行星上,有许多不同类型的矿石资源,每种矿石都有不同的重要性和价值。你需要选择哪些矿石带回地球,但你的宇航舱有一定的容量限制。 

给定一个宇航舱,最大容量为 C。现在有 N 种不同类型的矿石,每种矿石有一个重量 w[i],一个价值 v[i],以及最多 k[i] 个可用。不同类型的矿石在地球上的市场价值不同。你需要计算如何在不超过宇航舱容量的情况下,最大化你所能获取的总价值。

输入描述

输入共包括四行,第一行包含两个整数 C 和 N,分别表示宇航舱的容量和矿石的种类数量。 

接下来的三行,每行包含 N 个正整数。具体如下: 

第二行包含 N 个整数,表示 N 种矿石的重量。 

第三行包含 N 个整数,表示 N 种矿石的价格。 

第四行包含 N 个整数,表示 N 种矿石的可用数量上限。

输出描述

输出一个整数,代表获取的最大价值。

输入示例

10 3 1 3 4 15 20 30 2 3 2

输出示例

90

提示信息

数据范围:
1 <= C <= 2000;
1 <= N <= 100;
1 <= w[i], v[i], k[i] <= 1000;

思路:

多重背包是物品数量有多个但不是无限,可以当成0-1背包问题,把物品数量展开为1。

C, N = list(map(int, input().split(" ")))

weights = list(map(int, input().split(" ")))
values = list(map(int, input().split(" ")))
nums = list(map(int, input().split(" ")))

# 多重背包问题,当成0-1背包,把物品展开来,然后再迭代容量后多迭代一次k nums[i] dp[j] = max(dp[j], dp[j-k*weights[i]+k*values[i])
# 注意组合!背包一维需要从后往前遍历!!!

def multi_pack(C, N,weights,values,nums):
    dp = [0] * (C+1)
    
    dp[0] = 0
    
    for i in range(N):
        for j in range(C,weights[i]-1 , -1):
        #for j in range(1, C+1): # 错误!0-1背包逆序,防止重复
            for k in range(1, nums[i]+1):
                # j大于k*weights[i]
                if j >= k*weights[i]:
                    dp[j] = max(dp[j], dp[j-k * weights[i]] + k* values[i])
                    
    return dp[C]

print(multi_pack(C, N,weights,values,nums))