动态规划入门到精通:GitHub_Trending/le/LeetCode-Book实战教程
你还在为动态规划题抓狂吗?
刷题时遇到动态规划(Dynamic Programming, DP)题就卡壳?看题解时觉得"我上我也行",自己做时却无从下手?本文将通过LeetCode-Book项目中的10+实战例题,从理论到代码实现,带你彻底掌握动态规划的解题方法论。
读完本文你将获得:
- 动态规划本质及与分治算法的核心差异
- 四步解题框架(状态定义→转移方程→初始状态→返回值)
- 6类经典题型全解析(含空间优化技巧)
- Python/Java/C++多语言实现代码
- 从入门到面试的完整学习路径
一、动态规划核心思想
1.1 动态规划定义
动态规划是一种通过将复杂问题分解为重叠子问题,并存储子问题解来避免重复计算的算法思想。与分治算法的主要区别在于:
| 特性 | 分治算法 | 动态规划 |
|---|---|---|
| 子问题关系 | 独立不重叠 | 存在重叠子问题 |
| 优化目标 | 一般问题的解 | 最优解(最大值/最小值) |
| 存储需求 | 无需存储中间结果 | 需要存储子问题解 |
1.2 两个核心特性
重叠子问题:不同问题包含相同子问题,如斐波那契数列中f(5)和f(4)都需要计算f(3)
最优子结构:问题的最优解包含子问题的最优解,如蛋糕切割问题中,最优切割方案必然包含子段的最优切割
1.3 四步解题框架
- 状态定义:定义
dp[i]或dp[i][j]代表的具体含义 - 转移方程:建立子问题之间的关系(核心难点)
- 初始状态:确定基础子问题的解
- 返回值:确定最终要返回的dp值
二、基础题型实战
2.1 一维DP:跳跃训练(斐波那契变种)
问题:青蛙一次可跳1或2级台阶,求跳n级台阶的方法数
解题思路:
- 状态定义:
dp[i]表示跳i级台阶的方法数 - 转移方程:
dp[i] = dp[i-1] + dp[i-2](最后一步跳1级或2级) - 初始状态:
dp[0]=1, dp[1]=1 - 空间优化:用两个变量交替存储,空间复杂度从O(n)降至O(1)
代码实现:
class Solution:
def trainWays(self, num: int) -> int:
a, b = 1, 1
for _ in range(num):
a, b = b, (a + b) % 1000000007
return a
class Solution {
public int trainWays(int num) {
int a = 1, b = 1, sum;
for(int i = 0; i < num; i++){
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return a;
}
}
2.2 最大子数组和( Kadane算法 )
问题:给定整数数组,求具有最大和的连续子数组
解题思路:
- 状态定义:
dp[i]表示以第i个元素结尾的最大子数组和 - 转移方程:
dp[i] = max(nums[i], dp[i-1]+nums[i]) - 初始状态:
dp[0] = nums[0] - 空间优化:直接修改原数组,空间复杂度O(1)
代码实现:
class Solution:
def maxSales(self, sales: List[int]) -> int:
for i in range(1, len(sales)):
sales[i] += max(sales[i - 1], 0)
return max(sales)
复杂度分析:
- 时间复杂度:O(n),只需遍历一次数组
- 空间复杂度:O(1),原地修改数组
三、二维DP与空间优化
3.1 珠宝的最大价值(二维DP)
问题:m×n网格中每个单元格有价值,从左上角到右下角(只能右/下移动),求最大价值和
解题思路:
- 状态定义:
dp[i][j]表示到达(i,j)的最大价值 - 转移方程:
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j] - 空间优化:直接修改原网格,省去dp数组
代码实现:
class Solution:
def jewelleryValue(self, frame: List[List[int]]) -> int:
m, n = len(frame), len(frame[0])
for j in range(1, n): # 初始化第一行
frame[0][j] += frame[0][j - 1]
for i in range(1, m): # 初始化第一列
frame[i][0] += frame[i - 1][0]
for i in range(1, m):
for j in range(1, n):
frame[i][j] += max(frame[i][j - 1], frame[i - 1][j])
return frame[-1][-1]
3.2 最长递增子序列(O(n²)到O(n log n)优化)
问题:求数组中最长严格递增子序列的长度
动态规划解法:
- 状态定义:
dp[i]表示以nums[i]结尾的最长递增子序列长度 - 转移方程:
dp[i] = max(dp[j]+1 for j < i if nums[j] < nums[i]) - 时间复杂度:O(n²),空间复杂度:O(n)
二分优化解法:
- 维护 tails 数组,tails[i]表示长度为i+1的子序列尾部元素
- 二分查找当前元素可插入的位置,更新tails数组
- 时间复杂度:O(n log n),空间复杂度:O(n)
代码实现:
# O(n log n)解法
class Solution:
def lengthOfLIS(self, nums: [int]) -> int:
tails, res = [0] * len(nums), 0
for num in nums:
i, j = 0, res
while i < j:
m = (i + j) // 2
if tails[m] < num:
i = m + 1
else:
j = m
tails[i] = num
if j == res:
res += 1
return res
四、经典问题实战
4.1 买卖股票的最佳时机(状态压缩)
问题:只能一次买入卖出,求最大利润
解题思路:
- 遍历过程中记录最低价格和最大利润
- 状态压缩:用两个变量替代dp数组
class Solution:
def maxProfit(self, prices: List[int]) -> int:
cost, profit = float('+inf'), 0
for price in prices:
cost = min(cost, price)
profit = max(profit, price - cost)
return profit
4.2 打家劫舍(相邻约束问题)
问题:不能抢劫相邻房屋,求最大抢劫金额
解题思路:
- 状态定义:
dp[i]表示前i间房屋的最大抢劫金额 - 转移方程:
dp[i] = max(dp[i-1], dp[i-2]+nums[i]) - 空间优化:用两个变量交替存储
class Solution:
def rob(self, nums: List[int]) -> int:
cur, pre = 0, 0
for num in nums:
cur, pre = max(pre + num, cur), cur
return cur
4.3 数字翻译(字符串DP)
问题:0-25对应A-Z,求数字有多少种翻译方法
解题思路:
- 状态定义:
dp[i]表示前i位数字的翻译方法数 - 转移方程:
dp[i] = dp[i-1] + (dp[i-2] if 10<=num<=25 else 0)
class Solution:
def translateNum(self, num: int) -> int:
s = str(num)
a = b = 1
for i in range(2, len(s) + 1):
a, b = (a + b if "10" <= s[i-2:i] <= "25" else a), a
return a
五、动态规划优化技巧总结
5.1 空间优化方法
| 方法 | 适用场景 | 空间复杂度优化 |
|---|---|---|
| 变量替代数组 | 只依赖前1-2个状态 | O(n) → O(1) |
| 原地修改 | 二维DP且只依赖相邻状态 | O(mn) → O(1) |
| 滚动数组 | 依赖前k个状态 | O(nk) → O(k) |
5.2 时间优化方法
| 方法 | 适用场景 | 时间复杂度优化 |
|---|---|---|
| 二分查找 | 递增/递减子序列问题 | O(n²) → O(n log n) |
| 前缀和 | 区间和问题 | O(n²) → O(n) |
| 状态压缩 | 高维状态问题 | 指数级 → 多项式级 |
六、学习路径与资源推荐
6.1 动态规划学习路径
6.2 LeetCode-Book项目使用指南
-
项目获取
git clone https://gitcode.com/GitHub_Trending/le/LeetCode-Book -
重点目录
leetbook_ioa/docs/:图解算法数据结构配套文档sword_for_offer/docs/:剑指Offer题解selected_coding_interview/docs/:精选面试题
-
推荐刷题顺序
- 动态规划解题框架.md
- 斐波那契数列 → 爬楼梯 → 最大子数组
- 最长递增子序列 → 打家劫舍 → 买卖股票系列
- 区间DP → 状态压缩DP
七、总结与展望
动态规划作为算法面试的"拦路虎",掌握它需要理解其本质而非死记硬背。本文通过LeetCode-Book项目中的实战例题,系统讲解了从基础到进阶的动态规划知识,包括:
- 核心思想:重叠子问题与最优子结构
- 解题框架:状态定义→转移方程→初始状态→返回值
- 优化技巧:空间压缩与时间优化方法
- 实战经验:6类经典题型全解析
记住,动态规划的关键在于多练多总结,建议按照本文提供的学习路径,每天至少练习1道DP题,坚持1个月必能看到显著进步!
收藏本文,下次刷DP题时对照实战,效率提升300%!
下期预告:《贪心算法 vs 动态规划:如何选择最优解法》
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



