583 字
3 分钟
代码随想录算法训练营第三十三天 | 动态规划 Part06
322. 零钱兑换
思路:
使用带记忆化的递归,即自顶向下 + 备忘录优化。
注意:备忘录初始化为 -2,代表未计算;-1 代表无解。
class Solution: def coinChange(self, coins: List[int], amount: int) -> int: # 初始化备忘录,-2表示尚未计算 self.memo = [-2] * (amount + 1) return self.dp(coins, amount)
def dp(self, coins, amount): # base case:目标金额为0 if amount == 0: return 0 # base case:目标金额为负,表示无解 if amount < 0: return -1 # 如果已经计算过,直接返回备忘录的值 if self.memo[amount] != -2: return self.memo[amount]
res = float('inf') for coin in coins: sub = self.dp(coins, amount - coin) # 如果子问题无解,跳过 if sub == -1: continue res = min(res, sub + 1)
# 保存子问题结果到备忘录 self.memo[amount] = -1 if res == float('inf') else res return self.memo[amount]
279. 完全平方数
思路:
完全背包问题。每个数 j*j
可以使用无限次。
dp[i] 表示和为 i 的完全平方数最少数量。
class Solution: def numSquares(self, n: int) -> int: # 初始化dp数组,dp[i]表示组成i的最少完全平方数数量 dp = [float('inf')] * (n + 1) dp[0] = 0 # 和为0时需要0个数
# 外层遍历背包容量 for i in range(1, n + 1): # 内层遍历所有平方数物品 for j in range(1, int(i**0.5) + 1): # 状态转移:dp[i] = min(dp[i], dp[i - j*j] + 1) dp[i] = min(dp[i], dp[i - j * j] + 1)
return dp[n]
139. 单词拆分
思路: 自顶向下 + 备忘录。记忆化搜索从字符串某一位置开始是否可以拆分。
class Solution: def wordBreak(self, s: str, wordDict: List[str]) -> bool: # 初始化备忘录,-1表示未计算,0表示false,1表示true self.memo = [-1] * len(s) return self.dp(s, 0, wordDict)
def dp(self, s, i, wordDict): # 如果从第i个字符开始可以成功拆分,返回True if i == len(s): return True # 如果已经计算过,直接返回结果 if self.memo[i] != -1: return self.memo[i] == 1
for word in wordDict: length = len(word) # 如果剩余长度不足一个单词,跳过 if i + length > len(s): continue # 取出s中从i开始、长度为length的子串 sub = s[i:i + length] if sub != word: continue # 如果子问题成功,记下结果并返回 if self.dp(s, i + length, wordDict): self.memo[i] = 1 return True
# 所有尝试都失败,记为false self.memo[i] = 0 return False
代码随想录算法训练营第三十三天 | 动态规划 Part06
https://fuwari.vercel.app/posts/code_musings_day_thirty-three/