动态规划入门:核心思想与实战应用

说实话,动态规划(DP)是我在算法学习路上遇到的第一个真正意义上的「分水岭」。

很多人觉得它玄乎,其实说白了,DP 就是一种聪明的穷举——把大问题拆成小问题,记住小问题的答案,避免重复计算。嗯,就这么简单。

核心思想:记住过去,复用未来

动态规划的核心就八个字:「以空间换时间」

你想想看,如果一个问题可以分解成相互重叠的子问题,那我们每次算完一个子问题,就把结果存起来。下次再遇到同样的子问题,直接查表,不用重新算。

我在项目中遇到过这样一个场景:做路径规划时,要反复计算某个节点到终点的最短距离。如果不做记忆化,每次都要重新遍历整张图,数据量一大直接超时。后来用 DP 表存起来,速度提升了上百倍。

动态规划三要素:
  • 状态定义:dp[i] 表示什么?这是最关键的一步
  • 状态转移方程:如何从已知状态推导出未知状态
  • 初始条件:边界值怎么设

重叠子问题:为什么 DP 能省时间?

重叠子问题是 DP 能提速的根本原因。

什么叫重叠?就是不同的「大问题」会共享同一个「小问题」。比如斐波那契数列,F(5) 需要 F(4) 和 F(3),F(4) 又需要 F(3) 和 F(2)。你看,F(3) 被重复计算了两次。

如果不用 DP,递归树会指数级膨胀。用了 DP,每个子问题只算一次,复杂度从 O(2^n) 降到 O(n)。

我的判断技巧: 一个问题是否有重叠子问题,可以看它的递归树里有没有重复节点。画个树图,一目了然。

最优子结构:局部最优能推出全局最优

最优子结构听起来高大上,其实意思很简单:大问题的最优解,可以由子问题的最优解推导出来

举个例子,你要从北京到上海走最短路径。如果最优路径经过了济南,那么从北京到济南这一段,也一定是最短的。否则,你可以换一条更短的到济南的路,整体就更短了——这就矛盾了。

我曾经踩过一个坑:在某个资源分配问题里,我默认它有最优子结构,结果算出来的方案根本不对。后来发现,子问题之间会相互影响,不能独立求解。嗯,这里要注意——不是所有问题都有最优子结构

注意: 贪心算法也依赖最优子结构,但贪心只选当前最优,不回头看。DP 会考虑所有可能,选全局最优。这是本质区别。

与贪心的区别:一个短视,一个全局

很多初学者分不清 DP 和贪心。我建议你记住一句话:贪心是「走一步看一步」,DP 是「走一步看全局」

对比维度 动态规划 贪心算法
决策方式 考虑所有可能,选最优 只选当前最优
子问题依赖 子问题之间有重叠 子问题通常独立
正确性 保证全局最优 不一定全局最优
典型问题 背包问题、最短路径 活动选择、哈夫曼编码

举个生活中的例子:你去自助餐厅,贪心是「先拿眼前最大的那块肉」,DP 是「先观察一圈,规划好怎么拿才能吃回本」。哪个更优?显然是 DP,但代价是你要多花时间观察。

斐波那契数列:DP 的 Hello World

斐波那契数列是理解 DP 最好的入门题。咱们先看最朴素的递归写法:

// 朴素递归 —— 指数级复杂度
int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
}

这个写法的问题在哪?重复计算太多了。算 fib(5) 时,fib(3) 被算了两次,fib(2) 被算了三次。n 越大,重复越严重。

接下来,我们用记忆化搜索(自顶向下 DP)来优化:

// 记忆化搜索 —— O(n) 时间,O(n) 空间
int memo[100] = {0};

int fib(int n) {
    if (n <= 1) return n;
    if (memo[n] != 0) return memo[n];  // 查表
    memo[n] = fib(n-1) + fib(n-2);     // 存表
    return memo[n];
}

你看,只加了一个数组和一行判断,复杂度就从指数级降到了线性。这就是 DP 的魅力。

当然,我们还可以用自底向上的迭代写法:

// 自底向上 DP —— O(n) 时间,O(1) 空间
int fib(int n) {
    if (n <= 1) return n;
    int prev2 = 0, prev1 = 1;
    for (int i = 2; i <= n; i++) {
        int cur = prev1 + prev2;
        prev2 = prev1;
        prev1 = cur;
    }
    return prev1;
}

这个版本连数组都省了,只用两个变量滚动更新。空间复杂度 O(1),完美。

我的建议: 初学 DP 时,先写记忆化搜索。它最接近人的思维,不容易出错。等熟练了,再尝试自底向上迭代,追求更优的空间复杂度。

知识体系总览

下面这张图总结了本章的核心逻辑,我建议你多看几遍:

动态规划核心 核心思想 以空间换时间 记住子问题答案 三要素 状态定义 状态转移方程 初始条件 与贪心的区别 DP:全局最优 贪心:局部最优 重叠子问题 最优子结构 斐波那契数列 记忆化搜索(自顶向下) 迭代 DP(自底向上)

这张图把 DP 的核心脉络都串起来了。你从中心出发,左边是思想,中间是方法,右边是对比,底部是具体实现。学 DP 时,就按这个框架来,不容易乱。


公众号:蓝海资料掘金营,微信deep3321