动态规划
📚 动态规划(Dynamic Programming)学习笔记
欢迎来到我的算法学习笔记!动态规划 是算法面试和竞赛中的重中之重,也是许多复杂问题的核心解法。本篇将带你从零开始,建立对 DP 的直觉和系统性思维。
📌 目录(点击跳转)
什么是动态规划
动态规划(DP) 是一种通过把原问题分解为相对简单的子问题,并存储子问题的解以避免重复计算,从而高效求解最优化问题的方法。它的核心思想是 “记住你算过的,避免重复劳动”。
与分治法不同,DP 的子问题往往不是独立的,而是相互重叠的(例如递归树中的相同节点)。DP 通过 表格(数组) 记录中间结果,将指数级复杂度降为多项式级。
核心思想:最优子结构与重叠子问题
- 最优子结构:一个问题的最优解包含其子问题的最优解。即,我们可以通过组合子问题的最优解来构造原问题的最优解。
- 重叠子问题:在递归求解过程中,相同的子问题会被反复计算。DP 利用记忆化(Memoization)或自底向上的表格法消除这些重复。
例如,斐波那契数列 F(n)=F(n-1)+F(n-2) 中,F(3) 在计算 F(5) 和 F(4) 时被重复计算多次,DP 通过数组保存 F(3) 的值,下次直接使用。
DP 的适用场景
DP 通常用于解决最优化问题(求最大值、最小值、最少步数等)或计数问题(求方案数),并且具备以下两个特征:
- 问题可以分解为规模更小的相似子问题(最优子结构)。
- 子问题的解可以被多次复用(重叠子问题)。
典型的 DP 应用包括:
- 背包问题(0-1背包、完全背包)
- 字符串编辑(最长公共子序列、编辑距离)
- 路径规划(网格最短路径、硬币找零)
- 股票买卖、区间 DP、树形 DP 等。
DP 解题四步法
无论题目多么复杂,都可以遵循这四步来设计 DP 算法:
-
定义状态(状态表示)
确定dp[i]或dp[i][j]的含义。例如:dp[i]表示前i个物品能获得的最大价值,或dp[i][j]表示字符串 A 的前 i 个字符与 B 的前 j 个字符的最长公共子序列长度。 -
推导状态转移方程
分析当前状态与之前状态的关系,写出递推式。这是 DP 的核心,通常基于“最后一步”或“决策”来思考。 -
初始化(边界条件)
确定最基础的状态值,如dp[0] = 0或dp[i][0] = 0。边界处理不当会导致结果错误。 -
确定计算顺序
根据状态转移的依赖关系,决定循环方向(通常从小到大,也可能从大到小)。对于二维 DP,常见为双重循环。
此外,必要时进行空间优化(滚动数组)以降低内存消耗。
动态规划分类详解
🔹 线性 DP
核心思想:状态沿着一个方向(如一维数组或两个序列)线性递推,通常从 dp[i-1]、dp[i-1][j] 等前驱状态转移而来。
典型模型:最长上升子序列(LIS)、最长公共子序列(LCS)、最大子段和、编辑距离。
优化方向:LIS 可结合二分查找优化到 O(n log n);LCS 在特定数据下可用 bitset 加速。
📖 详细文章 | 📝 题单
🔹 多维 DP
核心思想:状态需要两个及以上的维度来描述(如 dp[i][j][k]),常见于二维网格、三维背包或资源分配问题。
典型模型:二维网格路径计数(含障碍)、多维背包(多种费用)、双序列加限制条件。
优化方向:滚动数组降维(如背包问题的一维优化);若维度过多,考虑状态压缩。
📖 详细文章 | 📝 题单
🔹 数位 DP
核心思想:解决与数字的数位构成相关的问题,通常统计区间 [L, R] 内满足特定性质的数字个数。
典型模型:不含某数字、数字和满足条件、能被某数整除、数位单调性等。
状态定义:dp[pos][state][limit][lead],其中 pos 为数位位置,state 为当前累积状态,limit 表示是否紧贴上界,lead 表示前导零。
实现技巧:记忆化搜索是主流,避免繁琐的循环判断。
📖 详细文章 | 📝 题单
🔹 树形 DP
核心思想:在树上进行动态规划,利用 DFS 回溯时从子节点收集信息,推算出父节点的最优值。
典型模型:树上最大独立集(选/不选当前节点)、树的直径、树形背包(依赖背包)、换根 DP(二次扫描)。
状态定义:通常 dp[u][0/1] 表示以 u 为根的子树,u 选或不选的最优值;树形背包则用 dp[u][j] 表示以 u 为根的子树内选择 j 个节点的最优值。
📖 详细文章 | 📝 题单
🔹 概率 DP
核心思想:处理随机过程的期望值或概率分布,通常从终止状态倒推,利用全期望公式或线性期望性质。
典型模型:掷骰子达到目标的期望步数、随机游走吸收概率、马尔可夫决策过程。
状态定义:dp[i] 表示从状态 i 到达目标状态的期望(或概率),转移方程常为 dp[i] = sum(p[i][j] * dp[j]) + w[i]。若存在环,需列方程高斯消元。
重要公式:全期望公式 E[X] = E[ E[X|Y] ] 是推导核心。
📖 详细文章 | 📝 题单
🔹 状压 DP
核心思想:用二进制位表示集合状态,将状态压缩为一个整数,在 n ≤ 20 时暴力枚举所有子集状态。
典型模型:旅行商问题(TSP)、哈密顿路径、覆盖问题(如铺瓷砖)、二分图完美匹配计数。
状态定义:dp[mask] 或 dp[mask][i],其中 mask 表示已选元素的集合,i 表示当前所在节点。
优化技巧:枚举子集 for (sub = mask; sub; sub = (sub-1) & mask),预处理辅助数组。
📖 详细文章 | 📝 题单
🔹 区间 DP
核心思想:状态定义在区间 [i, j] 上,通过枚举分界点将大区间拆分为两个或多个子区间,合并子区间的解得到当前区间的解。
典型模型:石子合并(最小/最大得分)、矩阵链乘、括号匹配、回文分割(最少切割)。
状态定义:dp[i][j] 表示区间 [i, j] 上的最优值。
计算顺序:按区间长度从小到大枚举,确保更短区间的值已计算。
📖 详细文章 | 📝 题单
🔹 数据结构优化
核心思想:利用数据结构(如线段树、树状数组、单调队列、平衡树)来加速状态转移,将原本 O(n²) 甚至 O(n³) 的 DP 优化到 O(n log n) 或 O(n)。
典型应用:
- 单调队列优化:用于滑动窗口类转移,如
dp[i] = max_{j∈[i-w, i-1]} (dp[j] + val[j])。 - 树状数组/线段树:用于维护前缀最值、二维偏序,如 LIS 的 O(n log n) 实现、带权 LIS。
- 斜率优化 / 凸包优化:将形如
dp[i] = min_j (dp[j] + cost(j,i))的转移转化为直线交点维护。
📖 详细文章 | 📝 题单
🔹 其他
涵盖内容:以上未归类的 DP 类型,如:
- 插头 DP / 轮廓线 DP:处理网格连通性、铺砖问题,状态表示轮廓线上的插头状态。
- 动态 DP(动态树分治):在树结构上支持点权修改,用矩阵乘法和线段树维护 DP 值。
- 博弈 DP:结合 SG 函数或极小极大搜索的 DP。
- 字符串 DP(如 AC 自动机 + DP):处理多模式串匹配计数。
说明:这些类型相对小众,但在高级竞赛中会出现,可根据兴趣学习。
📖 详细文章 | 📝 题单

浙公网安备 33010602011771号