← 首页 / 枚举最后一段:相册排版与内容分栏 待审核
recurrence · dp[t] = agg over s

枚举最后一段:相册排版与内容分栏

一排元素按固定顺序排列——一行图片、一列卡片、一段文字的单词——要把它们切成若干连续段(每行放哪几张、每栏放哪几件、每行排哪几个词)。每种切法有个代价,求代价最优的切法,就是最优分段 (optimal partition)。前端里的相册 justified layout内容分栏均衡都是它的实例,而工程里常见的贪心逐段只是局部最优——本页把两个场景都做成「贪心 vs DP 全局最优」的对照,直接呈现差距。

1 · 一个递推式:枚举「最后一段」

dp[t] =「最优地安排前 t 个元素」的答案。要算它,只需枚举最后一段从哪里起——设它是第 s+1..t 个元素,那么前 s 个的最优答案是子问题 dp[s],再加上这一段自身的代价:

dp[t] = agg over s in [0, t)  combine( dp[s], segCost(s, t−1) )

填完 dp[],沿记录的「最优起点」回溯,就还原出每一段的边界。两个场景只是换 segCost(段代价)与 agg(聚合)——相册是 Σ 段代价最小、段数不限;分栏是 max 段和最小、恰好切成 K 段。

为什么贪心不够? 贪心「一段填到不能再填就断」,只顾眼前那一段最满,却可能把糟糕的余量甩给最后一段(相册末行被拉得过高、分栏末栏堆积过高)。DP 把「最后一段起点」当成决策枚举一遍,让全局总代价最优——代价常常差出一个量级。下面两个 lab 左右并排,右侧 DP 面板在胜出时会高亮。

2 · 场景一:相册 justified layout

Flickr / Google Photos 那种相册:每行图片等高、右边缘对齐。一行装第 s+1..t 张图时,把它们按各自宽高比缩放到共同高度铺满容器宽度——图越多、越宽,共同高度就越矮。一行的代价 = 这个共同高度偏离目标行高的程度的平方 ((htargetH)/targetH)2((h - targetH) / targetH)^2;越贴近目标越好。总代价 = 各行代价之和,用最优分段最小化它。末行按真实相册的做法处理:只要在目标高度下排得下,就左对齐、保持目标高度、不拉伸也不计代价(排不下时才当满行)。拖目标行高滑块或换图组,看两种排法即时重排:

贪心每行只顾把当前行填满就断行、不看后面,行高常偏离目标(有的偏扁、有的偏高),还容易让最后几张图形成孤行;DP 统筹全局,让各行都更贴近目标行高,末行也更自然。差距虽不总是量级之大——这正是相册库普遍用贪心的原因——但在混合宽高比、目标行高偏大时 DP 明显更整齐(内容分栏那节的 DP 优势更悬殊)。单步区的元素尺高亮的是当前 dp[t] 选中的「最后一行」。

3 · 场景二:内容分栏均衡

N不同高度的卡片按顺序分到 K 栏,让最高的那一栏尽量矮(整体最均衡,视觉不头重脚轻)。这是经典的 min-max 划分:dp[k][t] = 把前 t 项切成 k 栏时「最高栏」的最小值,枚举最后一栏 s+1..t,与前 k1k-1 栏的最高栏取 max、再对 smin。调 K 或换数据:

贪心「填至平均高度就换栏」,余量往往堆在末栏,最高栏明显高于理论最优;DP 全局权衡,最高栏更矮。注意 CSS 原生的 column-count 走的正是顺序填栏的贪心思路,所以偶尔会出现末栏偏空 / 偏满。

4 · 同一个模型,还能做什么

「枚举最后一段 + 段代价 + 回溯」这套骨架适用面很广。最著名的一例是段落断行(Knuth–Plass):把一段文字的单词切成若干行,段代价 = 行内空格被拉伸 / 压缩程度的立方 + 惩罚,最小化总代价——就是 TeX 的断行、以及 CSS text-wrap: pretty 想逼近的效果,和贪心逐行(浏览器默认)相比更少出现「河流」与末行孤字。它与本页相册 justified 是同一族的最小化问题,只是段代价换成了排版的 badness。

判断一个前端排版 / 布局需求「是否采用 DP」,看它是不是**「按顺序切成连续段、每段有代价、要全局最优」**。是,就套本页这套引擎;换 segCost 就换场景。反之,若局部贪心已够好(多数瀑布流、普通换行),不必采用 DP。

🔗 相关链接

  • Minimum raggedness (Knuth–Plass) · Wikipedia 最优断行:把段落切成行使总 badness 最小的 DP,与本页相册 justified 同族。TeX 断行、text-wrap: pretty 的理论基础。
  • 折行与断词 · 本站 · typesetting CSS 属性层的换行控制:white-space / overflow-wrap / word-break,以及 text-wrap: balance / pretty —— 断行 DP 在浏览器里的落地。
  • 序列对齐 DP · 本站 · sequence-dp 另一族 DP:二维序列对齐 (LCS / 编辑距离 / DTW)。与本页的一维分段互为对照 —— 都靠「子问题 + 回溯」。
  • 背包问题九讲 · 本站 · dp 动态规划的思考艺术:状态、转移方程、循环方向,以及问法变化。分段 DP 的通用底层。