LeetCodeCampsDay38动态规划part06
LeetCodeCampsDay38动态规划part06
最小个数问题:零钱兑换、完全平方数
求排列问题:单词拆分
322. 零钱兑换
https://leetcode.cn/problems/coin-change/
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。
你可以认为每种硬币的数量是无限的。
示例 1:
1 | 输入:coins = [1, 2, 5], amount = 11 |
示例 2:
1 | 输入:coins = [2], amount = 3 |
示例 3:
1 | 输入:coins = [1], amount = 0 |
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
动态规划思路
完全背包、装满到target的最小物品数量的问题
-
dp定义与下标含义,dp[i][j]表示用物品0~i装满到j的最少物品个数
-
递推公式:凑足总额为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[i][j] = min(dp[i - 1][j], dp[i][j - coins[i]] + 1)
-
初始化:dp的大小:dp[len(coins)][amount + 1],并且全部初始化为无穷大;再对第一行、第一列进行初始化; 第一行,如果j能整除coins[0],则需要j//coins[0]个,否则初始化为0;第一列,全部为0
-
遍历顺序:先物品再容量,如果coins[i] > j,则dp[i][j] = dp[i - 1][j];否则dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i]] + 1)
-
举例
0, 1, 2, 3, 4, 5, 6
1 0, 1, 2, 3, 4, 5, 6
2 0, 1, 1, 2, 2, 3, 3
动态规划代码
- 时间复杂度: O(n * amount),其中 n 为 coins 的长度
- 空间复杂度: O(amount)
1 | class Solution: |
279. 完全平方数
https://leetcode.cn/problems/perfect-squares/
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
示例 1:
1 | 输入:n = 12 |
示例 2:
1 | 输入:n = 13 |
提示:
1 <= n <= 104
动态规划思路
和题目和322. 零钱兑换基本一样,完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品
物品应该只遍历完全平方数(我这里先求出了完全平方数的物品列表item)
-
数组与下标定义,dp[i][j]表示物品(完全平方数) 从0到i,构成完全平方数j的最小数量
-
递推公式:dp[i][j] = min(dp[i - 1][j], dp[i][j - item(i)] + 1); 卡哥说:dp[j] 可以由dp[j - i * i]推出, dp[j - i * i] + 1 便可以凑成dp[j]。 此时我们要选择最小的dp[j],所以递推公式:dp[j] = min(dp[j - i * i] + 1, dp[j]);
-
初始化:dp[n][n + 1]全初始化为float(‘inf’), 第一行(数字1),构成从0到n,如果j大于数字1则为j//i,第一列全是0
-
遍历顺序,先物品再容量
-
0, 1, 2, 3, 4, 5, 6, 7, 8,
1 0, 1, 2, 3, 4, 5, 6, 7, 8
4 0, 1, 2, 3, 1, 2, 3, 4, 2
动态规划代码
1 | class Solution: |
动态规划代码精简
1 | class Solution: |
139. 单词拆分
https://leetcode.cn/problems/word-break/
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
1 | 输入: s = "leetcode", wordDict = ["leet", "code"] |
示例 2:
1 | 输入: s = "applepenapple", wordDict = ["apple", "pen"] |
示例 3:
1 | 输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] |
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s
和wordDict[i]
仅由小写英文字母组成wordDict
中的所有字符串 互不相同
动态规划思路
把字符串s当成一个背包,wordDict就是物品,看能否刚好装满这个背包(每个物品可以使用多次),完全背包能否装满的问题
- dp含义与下标:dp[i][j],使用物品0~i组成一个长度为j的字符串,如果能组成就是true;如果使用一维dp,则dp[j]组成一个长度为j的字符串,如果能组成就是true
- 递推公式:本题里被遍历的对象是s里的每个字符,如s=applepenapple,则遍历:a, ap, app, appl, apple, applep…;需要双指针,一个i用来遍历每个字符,另一个j用来记录一个完整单词的起点;比如当前遍历到i='e’而j='p’即pe,再判断当前字段是否在wordDict中,而pe不在wordDict里;当遍历到i='n’而j='p’即pen,但pen在wordDict里,此时再判断dp[j]是否也是True,如果dp[j]为True表示j前面的词是可以被wordDict表示的,随后再令dp[i]=True;否则dp[i]=False
a | p | p | l | e | p | e | n | |
---|---|---|---|---|---|---|---|---|
j | i |
此时的当前字段为’pe’并不在wordDict中,令dp[i]为False
a | p | p | l | e | p | e | n | |
---|---|---|---|---|---|---|---|---|
j | i |
此时的字段’pen’在wordDict中,并且dp[j]为True(指的是p前面的apple词,通常情况下可能是appleapple等词是可以使用wordDict表达的),此时再令dp[i]=True;如果dp[j]为False,令dp[i]为False,表示哪怕pen可以被wordDict表示但前面的词不可以
-
dp初始化, dp大小为[False] * (len(s) + 1),其中第一个为空字符串;而令dp[0]=True,表示空字符串是一定可以被表示的
-
遍历顺序:前面也有题目是
组合数/排列数
-
如果问能否是否装满,还有两个子问题:
1、排列数 – 先遍历背包再遍历物品
2、组合数 – 先遍历物品再遍历背包
-
求组合数:动态规划:518.零钱兑换II
-
求最小数:动态规划:322. 零钱兑换 、动态规划:279.完全平方数
-
举例
输入
s =“leetcode”
wordDict = [“leet”,“code”]
标准输出
[True, False, False, False, False, False, False, False, False]
l
le
e
lee
ee
e
leet 找到第一个词
[True, False, False, False, True, False, False, False, False]
eet
et
t
leetc
eetc
etc
tc
c
leetco
eetco
etco
tco
co
o
leetcod
eetcod
etcod
tcod
cod
od
d
leetcode
eetcode
etcode
tcode
code 找到了第二个词
[True, False, False, False, True, False, False, False, True]
ode
de
e
最终输出
[True, False, False, False, True, False, False, False, True]
动态规划代码
- 时间复杂度:O(n^3),最差情况n^3,最好n^2
- 空间复杂度:O(n)
1 | class Solution: |