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
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

0-1 背包

动态规划 · 优化 / 取舍

装什么最值钱

有一个容量有限的背包和若干件物品,每件有重量和价值,每件要么整件装、要么不装(这就是「0-1」)。在不超重的前提下,怎么装总价值最大?这是最经典的动态规划入门题——和「编辑距离」一样填一张表,但填的是「最大价值」而非「最少代价」。

取,还是不取

设 dp[i][w] = 只考虑前 i 件物品、背包容量为 w 时能装出的最大价值。边界:没有物品(第 0 行)或容量为 0(第 0 列),价值都是 0。对第 i 件物品,只有两种选择:

装不下(它的重量 > 当前容量 w)——只能不取,dp[i][w] 直接沿用上一行dp[i-1][w];装得下——在不取(dp[i-1][w])和取(dp[i-1][w-重量] + 价值,即腾出重量后的最优再加上本物品)之间取较大者。

下面固定 4 件物品 A(重2,值3) B(重3,值4) C(重4,值5) D(重5,值6)、容量 5。点「下一步」逐格看:当前格琥珀环,它参考的「不取」上格和「取」的左上偏移格都黄色高亮——填入即变绿。走到底,右下角 = 7(选 A+B:重 2+3=5、值 3+4=7)。右侧代码随每一步同步高亮。

012345
∅000000
A0
B0
C0
D0

边界:没有物品(第 0 行)或容量为 0(第 0 列)时,最大价值都是 0

1function knapsack(w: number[], v: number[], W: number): number {
2 const m = w.length;
3 const dp = Array.from({ length: m + 1 }, () => Array(W + 1).fill(0));
4 for (let i = 1; i <= m; i++) {
5 for (let c = 1; c <= W; c++) {
6 if (w[i - 1] > c) {
7 dp[i][c] = dp[i - 1][c];
8 } else {
9 dp[i][c] = Math.max(
10 dp[i - 1][c], dp[i - 1][c - w[i - 1]] + v[i - 1]);
11 }
12 }
13 }
14 return dp[m][W];
15}
容量5
物品A(重2,值3) B(重3,值4) C(重4,值5) D(重5,值6)
1 / 22

每格只看上一行的两个格子就能定,共 O(物品数 × 容量) 格,远快于枚举 2ⁿ 种取法。这正是动态规划:把「前 i 件、容量 w」的子问题解存进表,后面的直接查表复用。递推里的「取 / 不取」两个来源,就是把「选择」显式地摆在两个邻居格上。

0-1 背包在哪里用

资源分配:预算/时间有限,选收益最大的项目组合。
装载 / 切割:容器装货、原料下料求最优。
DP 基石:完全背包、多重背包、子集和都由它变体而来。它和编辑距离分别是优化型与序列对齐型二维 DP 的代表。

想看另一种「填表」DP(求最少代价而非最大价值),回看 编辑距离——两页对照,能体会动态规划「定义状态 + 写对递推 + 逐格填表」的通用套路。