代码随想录 day41 动态规划part06 完全背包

322. 零钱兑换

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

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

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

思路

不同面额的硬币 coins, 一个总金额amount。 凑成dp[amout]的方法。

dp[j]: 背包j的最金额。
dp[j] = max(dp[j], dp[j - coin] + coin)

dp[0] = [0] * (amount + 1)
for j in range(1, amount + 1):
    for coin in coins:
        if j > = coin:
            dp[j] = max(dp[j], dp[j - coin] + coin])
            if dp[j] == j:continue
j = 1, coin = 1
dp[1] = 1
j = 2--- coin = 1, 2
j = 2, coin = 1
dp[2] = max(dp[2], dp[2-1] + 1) = max(0,1 + 1) = 1
j = 2, coin = 2
dp[2] = max(dp[2], dp[2-2]+2) = max(2,2) = 2

j = 3, coin = 1,2,
dp[3] = max(dp[3], dp[3-1] + 1) = max(0,2+ 1) = 3
j = 3, coin = 2
dp[3] = max(dp[3], dp[3-2]+2) = max(3,1 + 2) = 3

j = 4, coin = 1,2,
dp[4] = max(dp[4], dp[4-1] + 1) = max(0,3 + 1) = 4
j = 3, coin = 2
dp[4] = max(dp[4], dp[4-2]+2) = max(4,2 + 2) = 4

j = 5, coin = 1,2,5
dp[5] = max(dp[5], dp[5-1] + 1) = max(0,4 + 1) = 5
j = 5, coin = 2
dp[5] = max(dp[5], dp[5-2]+2) = max(5,3 + 2) = 5

j = 5, coin = 5
dp[5] = max(dp[5], dp[5-5]+5) = max(5,5) = 5

j = 6, coin = 1,2,5
dp[6] = max(dp[6], dp[6-1] + 1) = max(0,5 + 1) = 6
...

j = 6, coin = 1,2,5
dp[11] = max(dp[11], dp[11-1] + 1) = max(0,dp[10] + 1) = ..
dp[11] = max(dp[11], dp[11-2] + 2) = max(0,dp[9] + 2) = ..
dp[11] = max(dp[11], dp[11-5] + 5) = max(0,dp[6] + 5) = max(dp[11], 11) = 11

能否凑出 amount

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [0] * (amount + 1)
        for j in range(1, amount + 1):
            for coin in coins:
                if j >= coin:
                    dp[j] = max(dp[j], dp[j - coin] + coin)
                    if dp[j] == j:
                        continue
        if dp[amount] == amount:return amount
        else:return -1

再读一遍题目:

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

思路

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

天菩萨,人家要的是凑成金额的最少的银币数, 不是能不能凑出。

dp[j] : 凑出 j 需要的最少硬币为dp[j]
dp[j] = min(dp[j], dp[j-coin] + 1)

dp[j] 如何初始化
dp = [float('inf')] * (amount + 1)
dp[0] = 0

for j in range(1, amount + 1):
    for  coin in coins:
        if j >= coin and dp[j-coin] != 'inf':
            dp[j] = min(dp[j], dp[j - coin] + 1)
if dp[amount] == 'inf':
    return -1
return dp[amount]


## 举例说明
coins = [1,2,5], amount = 11

j = 1, coins = 1
dp[1] = 1

j = 2, coin = 1
dp[2] = min(inf, 1 + 1) =2
j = 2, coin = 1
dp[2] = min(2,1) = 1

j = 3
dp[3] = (dp[2] + 1) = 2
dp[3] = (2, dp[3-2] + 1) = 2

j = 4
dp[4] = 2

j = 5
dp[5] = 1

j = 11
dp[11] = 3


code python

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

279.完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

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

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

示例 1:

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

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

1 <= n <= 10^4
#算法公开课

思路

n,  返回和为 n 的完全平方数的 最少数量
和: 至少两个数

假设完全平方数 1 <= 完全平方数 < n

coins = [v**2 for v in range(1,n // 2 + 1)  if v ** 2 < n]

dp[j]  返回和为 j 的完全平方数的 最少数量
coins = [v**2 for v in range(1,n // 2 + 1)  if v ** 2 < n]

dp[j] = min(dp[j], dp[j - coin] + 1)
for coin in coins:
    dp[coin] = 1

for j in range(2, n+1):
    for coin in coins:
        if j > coin and dp[j - coin] != float('inf'):
            dp[j] = min(dp[j], dp[j-coin] + 1)
return dp[n]

## 举例
n = 12
coins = [1, 4, 9]

j = 2, coin = 1
dp[2] = 1 + 1 = 2

j = 3
dp[3] = dp[2] + 1 = 3

dp[4] = 1

dp[5] = 2


code python

# 0 + n(n为平方数) = 1
class Solution:
    def numSquares(self, n: int):
        coins = [v ** 2 for v in range(0, (n + 1) // 2 + 1) if v ** 2 <= n]
        dp = [float('inf')] * (n+1)
        dp[0] = 0

        for coin in coins:
            dp[coin] = 1

        for j in range(1, n + 1):
            for coin in coins:
                if j >= coin and dp[j - coin] != float('inf'):
                    dp[j] = min(dp[j], dp[j - coin] + 1)
                    if dp[j] == 1: continue
        return dp[n]

139.单词拆分

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
注意你可以重复使用字典中的单词。

示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

思路

s --字符串, 非空单词列表wordDict, s的拆分为字典的一个或者多个在字典中出现的单词。
拆分原则,可以重复使用字典中的单词,假设字典中没有重复的单词。

是否可以被拆分, return True, False

dp[j]:长度为j的字符串能否被拆解 dp[j]

dp[j] = (str[i:j] in wordDict) and dp[j-i]
dp = [False] * (len(s) + 1)

dp[0] = True
if i = 4, s[:i+1] in wordDict, dp[i] = (str[i:j] in wordDict) and dp[j-i] = True and dp[0] = True---> dp[0]=True

# dictDict的最大长度和最小长度
lenonly = sorted(set([len(v) for v in wordDict]))
for j in range(1, len(s)):
    for i in lenonly:
        if j >= i and dp[j-i] == True and not dp[j]:
            dp[j] = (str[j-i :j] in wordDict)    ### s = "catsandog",
# 打印

code python

class Solution:
    def wordBreak(self, s: str, wordDict):
        dp = [False] * (len(s) + 1)
        dp[0] = True
        #  dictDict的单词长度
        lenonly = sorted(set([len(v) for v in wordDict]))
        for j in range(1, len(s) + 1):
            for i in lenonly:
                if j >= i and dp[j - i] and not dp[j]:
                    dp[j] = s[j - i:j] in wordDict    ### s = "catsandog",
        return dp[-1]

总结 from代码随想录

周一

动态规划:377. 组合总和 Ⅳ 中给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数(顺序不同的序列被视作不同的组合)。

题目面试虽然是组合,但又强调顺序不同的序列被视作不同的组合,其实这道题目求的是排列数!

递归公式:dp[i] += dp[i - nums[j]];

这个和前上周讲的组合问题又不一样,关键就体现在遍历顺序上!

在动态规划:518.零钱兑换II

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

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

如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!

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

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target + 1, 0);
        dp[0] = 1;
        for (int i = 0; i <= target; i++) { // 遍历背包
            for (int j = 0; j < nums.size(); j++) { // 遍历物品
                if (i - nums[j] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]]) {
                    dp[i] += dp[i - nums[j]];
                }
            }
        }
        return dp[target];
    }
};

周二

爬楼梯之前我们已经做过了,就是斐波那契数列,很好解,但动态规划:70. 爬楼梯进阶版(完全背包)我们进阶了一下。

改为:每次可以爬 12.....、m 个台阶。问有多少种不同的方法可以爬到楼顶呢?

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

每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。

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

此时大家应该发现这就是一个完全背包问题了!

和昨天的题目动态规划:377. 组合总和 Ⅳ (opens new window)基本就是一道题了,遍历顺序也是一样一样的!

代码如下:

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];
    }
};

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

周三

动态规划:322.零钱兑换 (opens new window)给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数(每种硬币的数量是无限的)。

这里我们都知道这是完全背包。

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

关键看遍历顺序。

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

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

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

外层for循环遍历物品,内层for遍历背包:

// 版本一
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 0; i < coins.size(); i++) { // 遍历物品
            for (int j = coins[i]; j <= amount; j++) { // 遍历背包
                if (dp[j - coins[i]] != INT_MAX) { // 如果dp[j - coins[i]]是初始值则跳过
                    dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
                }
            }
        }
        if (dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};
外层for遍历背包,内层for循环遍历物品:

// 版本二
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {  // 遍历背包
            for (int j = 0; j < coins.size(); j++) { // 遍历物品
                if (i - coins[j] >= 0 && dp[i - coins[j]] != INT_MAX ) {
                    dp[i] = min(dp[i - coins[j]] + 1, dp[i]);
                }
            }
        }
        if (dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};

周四

动态规划:279.完全平方数 (opens new window)给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少(平方数可以重复使用)。

如果按顺序把前面的文章都看了,这道题目就是简单题了。 dp[i]的定义,递推公式,初始化,遍历顺序,都是和动态规划:322. 零钱兑换 (opens new window)一样一样的。

要是没有前面的基础上来做这道题,那这道题目就有点难度了。

这也体现了刷题顺序的重要性。

先遍历背包,再遍历物品:

// 版本一
class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 0; i <= n; i++) { // 遍历背包
            for (int j = 1; j * j <= i; j++) { // 遍历物品
                dp[i] = min(dp[i - j * j] + 1, dp[i]);
            }
        }
        return dp[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 = 1; j <= n; j++) { // 遍历背包
                if (j - i * i >= 0) {
                    dp[j] = min(dp[j - i * i] + 1, dp[j]);
                }
            }
        }
        return dp[n];
    }
};

总结

cpp本周的主题其实就是背包问题中的遍历顺序!

求组合数:
动态规划:518.零钱兑换II 求排列数:
动态规划:377. 组合总和 Ⅳ 、
动态规划:70. 爬楼梯进阶版(完全背包)求最小数:
动态规划:322. 零钱兑换
动态规划:279.完全平方数

此时我们就已经把完全背包的遍历顺序研究的透透的了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值