# 动态规划基础
# 简介
动态规划(Dynamic Programming,简称DP)是一种通过将复杂问题分解为更简单的子问题来解决问题的方法。它的核心思想是:
- 将原问题分解为相互重叠的子问题
- 保存子问题的解以避免重复计算
- 自底向上或自顶向下地构建最终解
# 基本要素
# 1. 最优子结构
如果问题的最优解包含其子问题的最优解,我们就称该问题具有最优子结构性质。
# 2. 重叠子问题
在递归过程中,相同的子问题会被重复计算多次。动态规划通过保存子问题的解来避免重复计算。
# 3. 状态转移方程
描述了问题结果与子问题结果之间的数学关系,是动态规划的核心。
# 实现方法
# 1. 记忆化搜索(自顶向下)
// 斐波那契数列的记忆化搜索实现
function fibMemo(n, memo = {}) {
if (n <= 1) return n;
if (n in memo) return memo[n];
memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
return memo[n];
}
# 2. 动态规划(自底向上)
// 斐波那契数列的动态规划实现
function fibDP(n) {
if (n <= 1) return n;
let dp = new Array(n + 1);
dp[0] = 0;
dp[1] = 1;
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
# 常见优化方法
- 状态压缩:当我们只需要用到前几个状态时,可以只保存这些状态而不是整个dp数组
- 空间优化:有时可以用几个变量代替数组,将空间复杂度从O(n)优化到O(1)
- 预处理:某些情况下可以提前处理一些信息,以减少主循环中的计算量
# 应用场景
- 最优化问题(如最短路径、最小花费等)
- 计数问题(如路径数量、方案数等)
- 存在性问题(如是否存在满足条件的解)
# 解题步骤
- 确定状态和状态变量
- 确定状态转移方程
- 确定初始状态和边界条件
- 确定计算顺序(自顶向下或自底向上)
- 实现代码并优化
# 复杂度分析
- 时间复杂度:通常为O(状态数量 × 状态转移的计算量)
- 空间复杂度:通常为O(状态数量),但可以通过优化降低
# 练习建议
- 从简单问题开始,如斐波那契数列、爬楼梯等
- 理解并记忆常见的动态规划模式
- 多做题,培养对状态定义和转移方程的敏感度
- 注意总结每类问题的特点和解题思路