首页 › programming › 五小时拆墙:动态规划的万能骨架

五小时拆墙:动态规划的万能骨架

导语:被恐惧放大的算法关卡

如果你在程序员群体里做个调查,“你最怕的算法主题”里动态规划(Dynamic Programming,简称 DP)大概率稳居前三。它承载了太多传说:面试刷人利器、天赋测试器、“看题解都会,合上题解就废”。可讽刺的是,这个让无数人劝退的主题,核心思想用一句话就能说完:把算过的东西记下来,别算第二遍。

这轮要深读的,是 freeCodeCamp 频道上累计播放数百万次的算法长课《Dynamic Programming - Learn to Solve Algorithmic Problems & Master Recursion》。讲师 Alvin(算法练习平台 Structy 的作者)用约 5 个小时,只讲了一个动作:画递归调用树。整门课像做手术一样,把这个传说中最难的主题,拆成了“画树 → 递归实现 → 加缓存剪枝”三步可复制的流程。它解决的核心问题只有一个:动态规划不是灵光一现的天赋题,而是一套可以刻意练习的模式识别。这不是又一门“刷题大全”,而是一门“解题操作系统”安装课。

视频背景卡片

项目 内容
视频标题 Dynamic Programming - Learn to Solve Algorithmic Problems & Master Recursion
频道 freeCodeCamp.org(订阅量千万级的公益编程教育频道)
讲师 Alvin(算法练习平台 Structy 作者)
发布时间 2021 年
时长 约 5 小时
播放量 累计数百万(据 YouTube 公开页面,长期位居 freeCodeCamp 算法类课程前列)
核心主题 用“递归树 + 缓存 + 填表”三件套系统通关动态规划

核心内容拆解

1. 先拆掉心墙:DP 到底难在哪

课程开篇,Alvin 给动态规划下了一个极简定义:DP = 递归 + 缓存。他强调,DP 难的原因不是数学,而是没人教你“看见”递归如何展开。整个课程的方法论由此定调:拿到任何问题,先把它的递归结构画成一棵树,树画对了,代码只是翻译。(视频原话与课程立意)

延伸分析:这个定位非常诚实。市面上多数教程直接甩“状态转移方程”,等于让你背公理;而这门课先建立几何直觉,再谈公式。认知科学里这叫从“程序性记忆”入手——先会做,再理解,恰好和算法教学的普遍失败模式相反。

2. 从 fib 看指数爆炸是怎么发生的

课程从最朴素的斐波那契递归开始:fib(n) = fib(n-1) + fib(n-2)。Alvin 现场画出了这行代码的调用树——树每往下一层,节点数近乎翻倍。他逐层数给你看:算 fib(50),树上有约 2 的 50 次方量级的节点,也就是千万亿次函数调用,而其中绝大多数是在重复计算同几个数字。(视频核心演示)

延伸分析:这一节的价值在于把“重叠子问题”(overlapping subproblems)这个抽象名词变成看得见的形状:树上同一批节点被抄写了成千上万遍。DP 的全部收益都来自一个朴素动作——同一道子题,只算一次。

3. Memoization:给递归装上缓存

接下来是全场第一个“ magic 时刻”:在递归函数里加一个 JS 对象当备忘录——算过的结果存进去,每次递归先查表,查到就直接返回。还是那棵树,只是重复的子树被整枝剪掉,fib(50) 从千万亿次调用骤降到约 n 次线性级别。Alvin 把这套流程总结成课程里反复出现的口诀:make it work, then make it efficient——先让暴力递归跑对,再无脑加缓存变快。(视频核心方法论)

延伸分析:值得点破的是,memoization 就是缓存(caching)在算法里的样子。你写 React 时的 useMemo、写后端时的 Redis 缓存、数据库的查询缓存,和这里加的那行 if (n in memo) return memo[n],是同一个思想的不同方言。

4. gridTraveler:证明骨架可以迁移

第二个问题:一个 m×n 的网格,从左上角只能向右或向下走,走到右下角有几种走法?Alvin 用同样的画树法拆解:每一步两个分支(向右/向下),坐标不断缩小,直到边界。加缓存时他顺手演示了一个经典剪枝:gridTraveler(m, n) 和 gridTraveler(n, m) 是同一道子题,把 (1,3) 和 (3,1) 视为同一个 key,缓存命中率立刻翻倍。(视频演示)

延伸分析:这一节是全课的转折点——你会发现 fib 和 gridTraveler 完全不同的故事背后是完全相同的树形。这就是“DP 面试题换皮不换骨”的第一次实证。它对应的正是 LeetCode 62 题 Unique Paths,难度中等,但在骨架面前只是换个坐标说话。

5. canSum / howSum / bestSum:三胞胎看穿题型分层

课程中段最精彩的设计:同一组原料(一个目标数 targetSum、一个数字数组 numbers,可重复使用,问能否恰好凑出),Alvin 连做三题——canSum 只回答能否(返回布尔),howSum 返回任意一个组合(返回数组),bestSum 返回最短组合(返回最优解)。三个函数共用同一棵递归树,只在返回值上做文章。加上缓存后,复杂度从指数级压到“目标值 × 选项数”的多项式量级。(视频核心设计)

延伸分析:这个“三胞胎”其实是把面试题的三大问法(判定型、构造型、优化型)一次性拆穿了。背题的人会把“零钱兑换”当新题,而理解骨架的人一眼看出它是 bestSum 换了个硬币皮肤——对应 LeetCode 322。

6. construct 家族:字符串上的同一棵树

课程随后把同样的骨架搬进字符串世界:给定目标字符串和词表,canConstruct 问能否拼出,countConstruct 问有几种拼法,allConstruct 列出全部拼法。画树的方式从“数字缩小”变成“前缀剥掉”,但递归结构、缓存写法、复杂度分析,全部照旧。(视频演示)

延伸分析:这一族对应 LeetCode 139/140(Word Break I/II),是 DP 题里伪装性最强的类型——很多人栽在这里是因为只见过数字版 DP。这门课把数字、网格、字符串三种皮肤各过一遍,等于给“模式识别”上了三重保险。这也是我认为它比多数 DP 课更值得推荐的根本原因。

7. 通用配方:全课真正想让你背走的流程

如果要给 5 个小时提炼成一张流程卡,就是课程反复演练的这条:第一,把问题画成树——确认“每一步有哪些分支”和“什么算到底”(base case);第二,把树翻译成递归代码——此时它很慢但正确;第三,找出树上的重复子树——给递归函数加上由参数唯一确定的 memo key;第四,分析复杂度——时间等于“子问题个数 × 每个问题的合并成本”。(视频方法论总结)

延伸分析:注意一个容易被忽略的点:memo 的 key 必须由全部参数唯一决定。字符串题要拼 head + ',' + tail,网格题要用 (m,n) 有序对——key 设计错了,缓存就会“污染”。这个细节无数教程不讲,却是实际写代码时最常见的翻车点。

8. Tabulation:把递归倒过来写的自底向上

课程后半段引入了 DP 的另一副面孔:tabulation(打表法)。以 fib 为例——不写递归,先建一张长度 n+1 的表,table[i] 表示“f(i) 的答案”,从 table[1] = 1 出发,用一个 for 循环把整张表从左往右填满。gridTraveler 也被改写成一张二维表逐格累加。Alvin 对比了两种风格:memoization 是自顶向下的“拆问题”,tabulation 是自底向上的“攒答案”。(视频后半场主线)

延伸分析:工程视角下 tabulation 常更受欢迎——没有递归就没有栈深度限制(长输入下递归版可能爆栈),常数更小,而且状态之间只依赖前几项时还能把表压缩成几个变量,把空间从 O(n) 降到 O(1)。面试里的高分答法往往是:先用画树+memo 快速拿到正确解,再顺势改成 table 展示工程素养——恰好就是这门课的教学顺序。

知识结构图

flowchart TD
    A["动态规划 DP = 递归 + 缓存"] --> B["第一步:画递归调用树"]
    A --> C["关键前提:重叠子问题"]
    B --> D["朴素递归:指数级爆炸"]
    C --> E["Memoization 自顶向下"]
    D --> E
    E --> F["数量型:fib 与 gridTraveler"]
    E --> G["判定与构造:canSum howSum bestSum"]
    E --> H["字符串型:canConstruct 家族"]
    G --> I["同一棵树,三种返回值"]
    H --> I
    E --> J["Tabulation 自底向上"]
    J --> K["先建表再迭代填表"]
    K --> L["复杂度:指数级降为多项式"]

实战启示

看完这门课,你能带走一套可以直接执行的训练方案:

第一,按课程的顺序打三周。 第一周只练“画树”:fib、gridTraveler、canSum,每题先在纸上画完调用树再写代码;第二周过 canSum/bestSum 和 construct 两大家族,要求自己说出每题是“判定、构造、优化”里的哪一种;第三周学 tabulation,把 memo 版逐题改写成 table 版。每天 1-2 题,一个月后 DP 对你不再是玄学。

第二,面试时把思考过程外化。 这门课示范的画树动作,本身就是最好的面试话术:先讲暴力递归(证明你能拆问题),再指出重叠子树(证明你懂 DP),最后加 memo 并报复杂度(证明你懂工程)。面试官要的从来不是“秒出状态转移方程”,而是这条可追溯的推理链。

第三,把骨架映射到题库。 LeetCode 70(爬楼梯)≈ fib,62(Unique Paths)≈ gridTraveler,322(零钱兑换)≈ bestSum,139/140(Word Break)≈ construct 家族。做完课程后去这些题上验证迁移能力,会获得极强的正反馈。

第四,迁移到工程里。 memoization 就是缓存,tabulation 就是迭代填表——理解了这对孪生思想,你在做接口缓存、动态规划式资源配置、甚至 React 性能优化时,手里多了一套有名字的心智模型,而不是零散的技巧。

延伸学习路径

  1. Structy(structy.net)— 讲师 Alvin 自己的算法练习平台,课程同款题目体系,配套代码可以逐题验证。
  2. CP-Algorithms《Intro to Dynamic Programming》(cp-algorithms.com)— 进入进阶阶段的免费文字教程,覆盖背包、LIS 等经典模型。
  3. MIT 6.006《Introduction to Algorithms》(MIT OpenCourseWare,ocw.mit.edu)— 想从工程直觉升级到理论证明,看大学正版的 DP 讲授。
  4. GeeksforGeeks Dynamic Programming 专题(geeksforgeeks.org/dynamic-programming/)— 题型索引式的参考手册,适合当字典查。
  5. LeetCode 动态规划标签页(leetcode.com/tag/dynamic-programming/)— 按频次刷本课骨架对应的真题,检验迁移效果。

一句话总结

这门 5 小时的课证明了一件事:动态规划从不考验天赋,它只考验你愿不愿意先画一棵树——画对了树,剩下的只是给递归加一行缓存。

参考来源

  1. Dynamic Programming - Learn to Solve Algorithmic Problems & Master Recursion — freeCodeCamp — 本文分析的核心视频
  2. Structy — 讲师 Alvin 的算法练习平台 — 课程配套题库
  3. CP-Algorithms: Intro to Dynamic Programming — 进阶理论补充
  4. MIT 6.006 Introduction to Algorithms (Spring 2020) — 学院派 DP 讲授
  5. LeetCode: Dynamic Programming 标签 — 骨架迁移的真题演练场

本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入