V

算法可视化

ZHEN
分享到微博分享到 XGitHub 仓库个人主页
学习工具算法复杂度速查算法学习路径
数据结构数组链表栈队列树堆哈希表图字典树并查集LRU 缓存跳表线段树B+ 树布隆过滤器树状数组
经典排序算法冒泡排序鸡尾酒排序双调排序选择排序插入排序二分插入排序希尔排序归并排序自顶向下归并快速排序三路快排双轴快排堆排序计数排序基数排序桶排序
图算法Dijkstra 最短路Kruskal 最小生成树Prim 最小生成树Bellman-Ford 最短路拓扑排序Floyd 多源最短路强连通分量2-SAT最大流二分图匹配LCA 倍增欧拉路径
动态规划编辑距离0-1 背包完全背包最长公共子序列最长递增子序列硬币找零方案数石子合并旅行商 TSP树形 DP数位 DP换根 DP
回溯与搜索N 皇后子集生成全排列组合总和迷宫寻路岛屿数量单词搜索数独A* 寻路
字符串KMP 字符串匹配Rabin-KarpBoyer-MooreManacher后缀数组LCP / height 数组AC 自动机Z 函数
数学与数论埃氏筛线性筛欧几里得算法快速幂扩展欧几里得中国剩余定理欧拉函数米勒-拉宾FFTPollard's Rho
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

树形 DP(打家劫舍 III)

动态规划 · 状态挂节点 · 后序即拓扑

小偷上树了

房子连成一棵二叉树,直接相连的父子不能同偷,求最大金额。本页的树(层序 [4, 1, 5, 3, 6]):

      4          ← 根
     / \
    1   5
   / \
  3   6

线性打家劫舍在数组上「偷不偷第 i 家」;上了树,链变成了分叉——但 DP 的骨架不变:每个节点两个状态。

两态与后序

选它:孩子都不能选 → 值 + Σ 孩子的「不选」;不选它:孩子自由发挥 → Σ max(孩子两态)。算父亲前孩子必须先算完——后序遍历天然就是拓扑序。下表每行一个节点、两列两态:注意填表跳着走(LL → LR → L → R → 根),那正是后序的足迹;黄格是引用的孩子状态。右侧代码随每一步同步高亮。

选不选
根 4
L 1
R 5
LL 3
LR 6

二叉树上打家劫舍:父子不能同偷,求最大金额。树:根 4,左子 1(孩子 3、6)、右子 5。每个节点两个状态——选(值 + Σ孩子的不选)/ 不选(Σ max(孩子两态));孩子先算完父亲才能算 → 按后序填表

1function rob(root: Node | null): number {
2 const dfs = (nd: Node | null): [number, number] => {
3 if (!nd) return [0, 0]; // 空子树两态皆 0
4 const [ls, ln] = dfs(nd.left); // 后序:先算孩子
5 const [rs, rn] = dfs(nd.right);
6 const sel = nd.val + ln + rn; // 选它:孩子都不能选
7 const not = Math.max(ls, ln) + Math.max(rs, rn); // 不选:孩子自由
8 return [sel, not];
9 };
10 return Math.max(...dfs(root)); // 根取两态最大
11}
树(层序)[4, 1, 5, 3, 6]
后序LL → LR → L → R → 根
1 / 10

树形 DP 三要素

状态挂节点:dp[u][...] 描述「以 u 为根的子树」的最优解。
子树即子问题:树的递归结构就是 DP 的无后效性来源。
后序即拓扑:孩子先于父亲就绪,一趟 DFS O(n) 填完。
同款:没有上司的舞会(带权独立集)、二叉树直径、树的重心。

再往上走是树上背包(每棵子树当一组物品做分组背包)与换根 DP(第二趟 DFS 把「以每个点为根」的答案摊到 O(n))。至此 DP 大类九页、四种状态设计集齐:序列前缀(LIS)、区间(石子合并)、集合(TSP)、树(本页)。