动态规划

动态规划 (Dynamic Programming, DP)

像“带着备忘录爬山”一样,把问题拆成有重叠的子问题。为了避免重复计算,把已经算过的子问题结果记在“表格”里。下次再遇到同样的子问题,直接查表,用空间换时间。

经典问题推演:斐波那契数列

状态定义:dp[i] 表示第 i 个斐波那契数。
边界条件:dp[0] = 0, dp[1] = 1。
状态转移方程:dp[i] = dp[i-1] + dp[i-2]。
填表过程:从 i=2 开始,利用前面已经算好的 dp[1] 和 dp[0] 算出 dp[2],再算 dp[3]... 一路推到 dp[n]。

经典问题推演:爬楼梯问题 (Climbing Stairs)

问题描述:假设你正在爬楼梯,需要 n 阶你才能到达楼顶。每次你可以选择爬 1 个台阶或者 2 个台阶。请问你有多少种不同的方法可以爬到楼顶?

示例:输入 n = 3,输出 3(因为方法有:1+1+1,1+2,2+1)。

为什么用动态规划?

如果用暴力递归,你会发现计算 f(5) 时需要计算 f(4) 和 f(3),而计算 f(4) 时又要算一次 f(3)。大量的子问题被反复计算,导致时间复杂度呈指数级爆炸。而动态规划通过“记住”已经算过的结果,完美解决了这个问题。

核心思想:三大核心要素
最优子结构:当前问题的解,可以由子问题的解组合得到。
重叠子问题:在递归展开时,会反复遇到相同规模的子问题。
状态转移方程:找到当前状态与前序状态之间的数学关系。

核心代码

#include <stdio.h>

// 动态规划求解爬楼梯
int climbStairs(int n) {
    // 1. 边界条件:处理最基础的子问题
    if (n <= 2) return n; 
    
    // 2. 定义状态表(这里为了节省空间,用三个变量代替数组)
    int prev2 = 1; // f(1) = 1
    int prev1 = 2; // f(2) = 2
    int current;
    
    // 3. 状态转移方程:自底向上填表
    for (int i = 3; i <= n; i++) {
        current = prev1 + prev2; // f(i) = f(i-1) + f(i-2)
        prev2 = prev1;           // 状态滚动更新
        prev1 = current;
    }
    
    // 4. 返回最终结果
    return prev1; 
}

int main() {
    int n = 5;
    printf("爬到 %d 阶楼梯的方法数为:%d\n", n, climbStairs(n));
    return 0;
}

示例推演

以 n = 5 阶楼梯为例,我们来看看自底向上的“填表”过程:

第 1 步:确定边界条件(初始化)
目标:找到不需要推导就能直接得出答案的基础情况。
爬 1 阶:只有 1 种方法(迈 1 步)。所以 f(1) = 1
爬 2 阶:有 2 种方法(1+1,或直接迈 2 步)。所以 f(2) = 2

第 2 步:推导状态转移方程
目标:找出 f(n) 和前面状态的关系。
思考:要到达第 n 阶,你的最后一步只有两种可能:
从第 n-1 阶,跨 1 步上来。
从第 n-2 阶,跨 2 步上来。
结论:到达第 n 阶的方法数 = 到达 n-1 阶的方法数 + 到达 n-2 阶的方法数。
状态转移方程:f(n) = f(n-1) + f(n-2)

第 3 步:自底向上填表(迭代计算)
目标:利用已知推导未知,避免重复计算。
计算 f(3):f(2) + f(1) = 2 + 1 = 3
计算 f(4):f(3) + f(2) = 3 + 2 = 5
计算 f(5):f(4) + f(3) = 5 + 3 = 8
最终结果
爬到 5 阶楼梯共有 8 种不同的方法。

四步法:

定义状态:明确 dp 数组或变量的含义(如 f(n) 表示爬到第 n 阶的方法数)。
确定转移方程:找出状态之间的关系式(如 f(n) = f(n-1) + f(n-2))。
设置初始条件:给出最小子问题的解(如 f(1)=1, f(2)=2)。
选择计算顺序:通常采用自底向上(迭代填表)的方式。
空间优化技巧:当状态转移仅依赖于前几个状态时,可以使用滚动变量(如代码中的 prev1, prev2)代替一维数组,将空间复杂度从 \(O(n)\) 降至 \(O(1)\)

核心提炼

两大核心性质:必须同时具备最优子结构(大问题的最优解包含子问题的最优解)和重叠子问题(子问题会被反复计算)。
与分治的区别:分治的子问题独立,DP的子问题重叠;DP需要额外的表格空间。

posted @ 2026-07-13 20:05  Eddi^._.^  阅读(16)  评论(0)    收藏  举报