动态规划详解(二):从暴力递归到动态规划的完整优化之路
目录
一、什么是动态规划?—— 从人类直觉到算法思维
二、暴力递归:最直观的问题分解方式
1. 示例:斐波那契数列
2. 递归树分析(以n=5为例)
3. 问题暴露
三、第一次优化:记忆化搜索(Memoization)
1. 核心思想
2. 斐波那契优化实现
3. 复杂度分析
四、第二次进化:自底向上动态规划
1. 思维转变
2. 斐波那契DP实现
3. 空间优化(滚动数组)
五、经典案例:爬楼梯问题(LeetCode 70)
1. 问题描述
2. 暴力递归解法
3. DP优化实现
4. 状态转移方程推导
六、高阶案例:0-1背包问题
1. 问题描述
2. 暴力递归解法
3. 记忆化搜索优化
4. 动态规划终极形态
5. 空间压缩技巧(滚动数组)
七、动态规划解题方法论总结
1. 五步法流程
2. 优化路线图
3. 常见问题处理技巧
八、实战练习建议
一、什么是动态规划?—— 从人类直觉到算法思维
动态规划(Dynamic Programming, DP) 本质是一种通过"聪明的穷举"解决问题的思想。它的核心是发现重叠子问题和最优子结构,并通过存储中间结果避免重复计算。我们可以通过一个认知升级路线来理解它:
暴力递归 → 发现重复计算 → 记忆化搜索 → 推导状态转移 → 自底向上动态规划
二、暴力递归:最直观的问题分解方式
1. 示例:斐波那契数列
// 经典递归实现
public int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
2. 递归树分析(以n=5为例)
fib(5)
/ \
fib(4) fib(3)
/ \ / \
fib(3) fib(2) fib(2) fib(1)
...(继续展开)...
3. 问题暴露
重复计算:fib(3)计算2次,fib(2)计算3次
指数级复杂度:O(2^n) 时间复杂度,O(n) 栈空间
三、第一次优化:记忆化搜索(Memoization)
1. 核心思想
-
空间换时间:使用数组/HashMap存储已计算结果
-
剪枝优化:计算前先查询存储结构
2. 斐波那契优化实现
public int fibMemo(int n) {
int[] memo = new int[n+1];
Arrays.fill(memo, -1);
return dfs(n, memo);
}
private int dfs(int n, int[] memo) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
memo[n] = dfs(n-1, memo) + dfs(n-2, memo);
return memo[n];
}
3. 复杂度分析
时间复杂度:O(n) —— 每个子问题只计算一次
空间复杂度:O(n) 递归栈 + O(n) 存储空间