重叠子问题

概述

重叠子问题是指在递归求解过程中,同一个子问题被反复计算多次的现象。这是动态规划区别于分治法的关键特征。通过备忘录(自顶向下)或自底向上表格技术,可以消除冗余计算,将指数级时间复杂度降低到多项式级。

定义

重叠子问题(Overlapping Subproblems)

当一个递归算法不断将问题分解为子问题时,如果不同的递归分支会求解完全相同的子问题实例,则称该问题具有重叠子问题性质。

设递归算法的递归树中,子问题 被访问的次数为 。若存在某个子问题 使得 ,则存在重叠子问题。

核心性质

1. 与分治法的本质区别

分治法(如归并排序、快速排序)的子问题是互不重叠的——每个子问题只被求解一次。而动态规划面对的问题中,子问题空间虽然规模有限,但递归过程中会反复遇到相同的子问题。

特征分治法动态规划
子问题关系互不重叠大量重叠
子问题数量递归树的所有节点有限个子问题(表格大小)
典型复杂度

2. 钢条切割中的重叠示例

钢条切割 问题中,朴素递归调用 CUT-ROD(p, n) 时,子问题 (长度为1的钢条的最大收益)被重复计算多次。具体地,计算 时, 会在以下路径中被反复求解:

  • …(共 条路径到达

这导致朴素递归的时间复杂度为 ,而子问题总数仅为 个。

3. 备忘录方法(自顶向下)

备忘录方法维护一个表格 ,初始值为某个哨兵值(如 )。每次求解子问题前先查表,若已计算则直接返回;否则计算后存入表格。

MEMOIZED-CUT-ROD(p, n, r):
    if r[n] >= 0:
        return r[n]
    if n == 0:
        q = 0
    else:
        q = -∞
        for i = 1 to n:
            q = max(q, p[i] + MEMOIZED-CUT-ROD(p, n-i, r))
    r[n] = q
    return q

时间复杂度:(每个子问题只计算一次,每次计算需要 的时间)。

4. 自底向上方法

自底向上方法按照子问题的规模从小到大依次填表,确保计算每个子问题时,它所依赖的更小子问题已经计算完毕。

BOTTOM-UP-CUT-ROD(p, n):
    r[0] = 0
    for j = 1 to n:
        q = -∞
        for i = 1 to j:
            q = max(q, p[i] + r[j-i])
        r[j] = q
    return r[n]

时间复杂度:,空间复杂度:

5. 两种方法的等价性

备忘录方法和自底向上方法具有相同的渐近时间复杂度。它们的区别在于:

  • 备忘录:只计算实际需要的子问题(可能跳过某些子问题),但存在递归调用的常数开销
  • 自底向上:计算所有子问题,无递归开销,通常实际运行更快

章节扩展

参见