279.完全平方数
题目链接:279. 完全平方数 - 力扣(LeetCode)
思路
这道题和上次的零钱兑换很像:代码随想录算法训练营第五十三天(完全背包篇)| LeetCode 322. 零钱兑换-优快云博客
把完全平方数看作物品,可取用次数不限,总和n看作背包容量,这道题就是求将背包装满的最小物体个数。
1. dp数组定义
dp[j]: 总和为j的平方数的最小个数。
2. 确定递推公式
遍历到数i时,如果用它的平方,可能的最小个数为dp[j - i**2] + 1(加上它本身),如果这个值小于不加上它的平方时的值dp[j],就用它,否则保持不变。所以得到:
dp[j] = min(dp[j], dp[j - i**2] + 1)
3. 初始化
j为0时,根据递推公式,dp值应为0:如果j本身是完全平方数,那么它的dp值应该是1,即它的根,而当i遍历到j的根,根据公式:
dp[j] = min(dp[j], dp[j - i**2] + 1) = min(dp[j], dp[0] + 1),因此,dp[0]需要为0。对于非零下标,dp值应该为正无穷,才能保证在递推的时候才不会被初始值覆盖。
4. 遍历顺序
由于是求物体个数,不涉及遍历物体的顺序,所以可以外层遍历物体内层遍历背包,也可以相反。
5. 举例推导dp数组
以n = 5为例, dp状态图如下:
代码实现
class Solution(object):
def numSquares(self, n):
dp = [float('inf')] * (n + 1)
dp[0] = 0
for j in range(1, n+1):
for i in range(1, int(j ** 0.5) + 1):
if i**2 <= j:
dp[j] = min(dp[j], dp[j-i**2] + 1)
return dp[n]
139. 单词拆分
思路
1. dp数组定义
dp[j]: 长度为j的字符串能被字典中的单词组成。
2. 确定递推公式
如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。所以递推公式为:
if dp[i] and (s[i:j] in wordSet):
dp[j] = True
3. 初始条件
dp[i]的状态依赖于前面dp[j]的状态,那么dp[0]一定为true,否则之后就全是False了。
4. 遍历顺序
之前提到过,求排列应该外层遍历背包容量,内层遍历物体;求组合应该外层遍历物体,内层遍历背包容量。这道题是求排列,因为给定单词的每种组合含义是不一样的,都要考虑到是否符合。
e.g. 对字典[''life'', “nice’'], 我们要看是否能组成''nice life'',如果先遍历物体,先看‘life’,发现s没有从0开始的区间里含“life”,就再遍历‘nice’,发现s前面有’nice‘出现,于是继续看s后面的部分是否出现在字典里,但‘life’上一轮已经遍历过,不会重复了,所以结果显示False,但实际应该为True。
5. 举例推导dp[i]
以输入: s = "leetcode", wordDict = ["leet", "code"]为例,dp状态如下:
代码实现
class Solution(object):
def wordBreak(self, s, wordDict):
wordSet = set(wordDict)
dp = [False]*(len(s) + 1)
dp[0] = True
for j in range(1, len(s) + 1):
for i in range(j):
if dp[i] and (s[i:j] in wordSet):
dp[j] = True
break
return dp[len(s)]