动态规划

  • 背包问题
  • 动态规划
  • 背包问题 FAQ
  • 最长公共子串

背包问题

假设你是个小偷,背着一个可装 4 磅东西的背包,可盗窃的商品有:

商品 重量 价值
吉他 1 磅 1500 美元
音响 4 磅 3000 美元
笔记本电脑 3 磅 2000 美元

简单算法:尝试各种可能的商品组合。3 件商品需要计算 8 个集合,4 件商品需要 16 个,每增加一件翻倍。运行时间为 O(2ⁿ),非常慢。

动态规划

动态规划先解决子问题,再逐步解决大问题。每个动态规划算法都从一个网格开始。

背包问题的网格:各行为商品,各列为不同容量(1~4 磅)的背包。

填充吉他行:吉他重 1 磅,所有容量的背包都能装下,每个单元格填入吉他(1500 美元)。

1磅 2磅 3磅 4磅
吉他 1500 1500 1500 1500

填充音响行:音响重 4 磅。1~3 磅装不下,保持 1500。4 磅能装下音响,3000 > 1500,更新为 3000。

1磅 2磅 3磅 4磅
吉他 1500 1500 1500 1500
音响 1500 1500 1500 3000

填充笔记本电脑行:笔记本电脑重 3 磅。1~2 磅装不下,保持 1500。3 磅能装下,2000 > 1500,更新为 2000。4 磅时:装笔记本电脑(3 磅),余下 1 磅,查之前 1 磅的最大价值是 1500(吉他),总计 2000+1500=3500 > 3000,更新为 3500。

1磅 2磅 3磅 4磅
吉他 1500 1500 1500 1500
音响 1500 1500 1500 3000
笔记本电脑 1500 1500 2000 3500

答案:将吉他和笔记本电脑装入背包,价值最高为 3500 美元。

单元格值的计算公式

cell[i][j] = max(
    上一行同一列的值(cell[i-1][j]),
    当前商品的价值 + cell[i-1][j - 当前商品的重量]
)

背包问题 FAQ

  • 再增加一件商品:只需在网格中添加一行,无需重新计算之前的结果。
  • 行的排列顺序:无关紧要,结果相同。
  • 可以逐列填充吗:就背包问题而言无影响,但其他问题可能有影响。
  • 增加更小的商品(如 0.5 磅的项链):需要调整网格的粒度,增加更多列。
  • 可以偷商品的一部分吗:动态规划没法处理,但贪婪算法可以——按价值密度从高到低拿。
  • 旅游行程最优化:这也是背包问题,约束条件从容量变成时间。
  • 处理相互依赖的情况:动态规划无法处理。仅当每个子问题都是离散的(不依赖于其他子问题时)才管用。
  • 最优解可能导致背包没装满:完全可能,比如偷了一颗 3.5 磅的钻石,余下 0.5 磅什么都装不下。

最长公共子串

动态规划的另一个应用是找出两个字符串的最长公共子串(或最长公共子序列)。

假设用户输入了 hish,字典中有 fishvista,要找出最接近的单词。建立网格比较 hishfish

f i s h
h 0 0 0 1
i 0 1 0 0
s 0 0 2 0
h 0 0 0 3

如果两个字母相同,单元格值为左上方单元格的值加 1;否则为 0。最长公共子串长度为 3("ish")。

最长公共子序列:如果两个字母相同,值为左上方加 1;如果不同,取上方和左方的最大值。这种算法用于比较文件的差异(如 diff 命令)。

动态规划小结

  • 动态规划可帮助你在给定约束条件下找到最优解
  • 在问题可分解为彼此独立且离散的子问题时,可使用动态规划
  • 每种动态规划解决方案都涉及网格
  • 单元格中的值通常就是你要优化的值
  • 每个单元格都是一个子问题,考虑如何将问题分成子问题有助于找出网格的坐标轴
  • 动态规划无法处理子问题相互依赖的情况
  • 动态规划要么考虑拿走整件商品,要么不拿,无法处理拿走一部分的情况(用贪婪算法)