1. 动态规划与Hot100算法题解析
最近在刷题社区频繁看到"Hot100"这个关键词,作为算法工程师,我花了两个月时间系统刷完了LeetCode Hot 100的动态规划专题。动态规划(Dynamic Programming)作为算法面试的必考题型,在Hot100中占比超过20%,是准备面试的重中之重。本文将分享我从零基础到熟练掌握DP的完整心路历程,包含题型分类、解题模板和避坑指南。
提示:本文基于Python3实现,但解题思路适用于所有编程语言。建议先掌握递归和基础数据结构再学习动态规划。
1.1 Hot100动态规划题目分布
LeetCode Hot100精选了平台最高频的100道算法题,其中动态规划相关题目有:
- 经典背包问题(第416题 分割等和子集)
- 字符串处理(第72题 编辑距离)
- 路径问题(第64题 最小路径和)
- 买卖股票系列(第121题 买卖股票的最佳时机)
- 打家劫舍系列(第198题 打家劫舍)
这些题目覆盖了动态规划90%的考察场景,掌握后可以应对大多数算法面试。我建议按以下顺序攻关:
- 先掌握斐波那契数列等基础问题
- 然后攻克背包问题
- 最后处理字符串和路径类问题
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动态规划核心解题框架
2.1 动态规划三要素
所有DP问题都包含三个核心要素:
- 状态定义:明确dp数组的含义
- 状态转移方程:找出dp[i]与之前状态的关系
- 初始条件和边界情况:确定dp[0]等初始值
以第70题爬楼梯为例:
python复制def climbStairs(n: int) -> int:
if n <= 2: return n
dp = [0]*(n+1)
dp[1] = 1 # 初始条件
dp[2] = 2
for i in range(3, n+1):
dp[i] = dp[i-1] + dp[i-2] # 状态转移
return dp[n]
2.2 四种常见DP类型
2.2.1 线性DP
典型题目:第53题最大子数组和
python复制def maxSubArray(nums):
dp = [0]*len(nums)
dp[0] = nums[0]
for i in range(1, len(nums)):
dp[i] = max(nums[i], dp[i-1]+nums[i]) # 关键转移方程
return max(dp)
2.2.2 区间DP
典型题目:第5题最长回文子串
python复制def longestPalindrome(s):
n = len(s)
dp = [[False]*n for _ in range(n)]
res = ""
for l in range(n): # 枚举子串长度
for i in range(n):
j = i + l
if j >= n: break
if l == 0:
dp[i][j] = True
elif l == 1:
dp[i][j] = (s[i] == s[j])
else:
dp[i][j] = (s[i] == s[j] and dp[i+1][j-1])
if dp[i][j] and l+1 > len(res):
res = s[i:j+1]
return res
2.2.3 背包DP
典型题目:第416题分割等和子集
python复制def canPartition(nums):
total = sum(nums)
if total % 2 != 0: return False
target = total // 2
dp = [False]*(target+1)
dp[0] = True
for num in nums:
for i in range(target, num-1, -1): # 反向遍历避免重复计算
dp[i] = dp[i] or dp[i-num]
return dp[target]
2.2.4 状态机DP
典型题目:第121题买卖股票的最佳时机
python复制def maxProfit(prices):
n = len(prices)
dp = [[0]*2 for _ in range(n)]
dp[0][0] = 0 # 不持有
dp[0][1] = -prices[0] # 持有
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i]) # 只能买卖一次
return dp[-1][0]
3. Hot100高频DP题目精讲
3.1 第10题正则表达式匹配
这是Hot100中最难的DP问题之一,考察字符串匹配与状态转移设计。
python复制def isMatch(s: str, p: str) -> bool:
m, n = len(s), len(p)
dp = [[False]*(n+1) for _ in range(m+1)]
dp[0][0] = True
for j in range(1, n+1):
if p[j-1] == '*':
dp[0][j] = dp[0][j-2]
for i in range(1, m+1):
for j in range(1, n+1):
if p[j-1] == '.' or p[j-1] == s[i-1]:
dp[i][j] = dp[i-1][j-1]
elif p[j-1] == '*':
dp[i][j] = dp[i][j-2] # 匹配0次
if p[j-2] == '.' or p[j-2] == s[i-1]:
dp[i][j] |= dp[i-1][j] # 匹配1次或多次
return dp[m][n]
3.2 第32题最长有效括号
利用栈和DP的混合解法:
python复制def longestValidParentheses(s: str) -> int:
n = len(s)
dp = [0]*n
res = 0
for i in range(1, n):
if s[i] == ')':
if s[i-1] == '(':
dp[i] = (dp[i-2] if i >= 2 else 0) + 2
elif i-dp[i-1] > 0 and s[i-dp[i-1]-1] == '(':
dp[i] = dp[i-1] + (dp[i-dp[i-1]-2] if (i-dp[i-1]) >= 2 else 0) + 2
res = max(res, dp[i])
return res
4. 动态规划优化技巧
4.1 空间优化:滚动数组
多数DP问题可以将空间复杂度从O(n)降到O(1),以斐波那契为例:
python复制def fib(n):
if n < 2: return n
a, b = 0, 1
for _ in range(2, n+1):
a, b = b, a + b
return b
4.2 状态压缩:位运算
适用于状态数较少的情况,如第464题我能赢吗:
python复制def canIWin(maxChoosableInteger, desiredTotal):
if maxChoosableInteger >= desiredTotal: return True
if (1 + maxChoosableInteger) * maxChoosableInteger // 2 < desiredTotal:
return False
memo = {}
def dfs(used, remaining):
if remaining <= 0: return False
if used in memo: return memo[used]
for i in range(1, maxChoosableInteger + 1):
if not (used & (1 << i)):
if not dfs(used | (1 << i), remaining - i):
memo[used] = True
return True
memo[used] = False
return False
return dfs(0, desiredTotal)
5. 常见错误与调试技巧
5.1 初始化错误
警告:dp数组初始化必须考虑所有边界情况。我在做第62题不同路径时,曾错误初始化dp数组导致越界。
正确做法:
python复制def uniquePaths(m, n):
dp = [[1]*n for _ in range(m)] # 第一行和第一列都初始化为1
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]
5.2 状态转移遗漏
第139题单词拆分容易遗漏转移条件:
python复制def wordBreak(s, wordDict):
wordSet = set(wordDict)
dp = [False]*(len(s)+1)
dp[0] = True
for i in range(1, len(s)+1):
for j in range(i):
if dp[j] and s[j:i] in wordSet: # 必须检查所有可能的j
dp[i] = True
break
return dp[-1]
5.3 打印DP表调试
当DP结果不符合预期时,打印中间状态是最有效的调试方法:
python复制def print_dp_table(dp):
for row in dp:
print(row)
6. 刷题路线建议
根据我的实战经验,推荐以下刷题顺序:
-
基础阶段(1-2周):
- 第70题 爬楼梯
- 第53题 最大子数组和
- 第198题 打家劫舍
-
进阶阶段(2-3周):
- 第322题 零钱兑换
- 第300题 最长递增子序列
- 第1143题 最长公共子序列
-
高手阶段(3-4周):
- 第10题 正则表达式匹配
- 第32题 最长有效括号
- 第312题 戳气球
每周保持3-5道DP题的练习量,每道题至少做两遍:第一遍自己思考,第二遍优化解法。我建立了一个错题本记录典型错误,三个月内DP正确率从30%提升到了85%。
