# 动态规划基础

# 简介

动态规划(Dynamic Programming,简称DP)是一种通过将复杂问题分解为更简单的子问题来解决问题的方法。它的核心思想是:

  1. 将原问题分解为相互重叠的子问题
  2. 保存子问题的解以避免重复计算
  3. 自底向上或自顶向下地构建最终解

# 基本要素

# 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];
}

# 常见优化方法

  1. 状态压缩:当我们只需要用到前几个状态时,可以只保存这些状态而不是整个dp数组
  2. 空间优化:有时可以用几个变量代替数组,将空间复杂度从O(n)优化到O(1)
  3. 预处理:某些情况下可以提前处理一些信息,以减少主循环中的计算量

# 应用场景

  1. 最优化问题(如最短路径、最小花费等)
  2. 计数问题(如路径数量、方案数等)
  3. 存在性问题(如是否存在满足条件的解)

# 解题步骤

  1. 确定状态和状态变量
  2. 确定状态转移方程
  3. 确定初始状态和边界条件
  4. 确定计算顺序(自顶向下或自底向上)
  5. 实现代码并优化

# 复杂度分析

  • 时间复杂度:通常为O(状态数量 × 状态转移的计算量)
  • 空间复杂度:通常为O(状态数量),但可以通过优化降低

# 练习建议

  1. 从简单问题开始,如斐波那契数列、爬楼梯等
  2. 理解并记忆常见的动态规划模式
  3. 多做题,培养对状态定义和转移方程的敏感度
  4. 注意总结每类问题的特点和解题思路