Skip to content

动态规划与其他算法

核心区别

理解 DP 与其他算法的边界,什么时候用 DP,什么时候不用。


DP vs 分治

相同点

  • 都是把问题分解为子问题
  • 都是递归求解

不同点

维度分治动态规划
子问题独立的可能重叠
结果合并子问题结果可能复用子问题结果
记忆化可选通常必要
示例归并排序、快速排序斐波那契、背包
typescript
// 分治 - 子问题独立
function mergeSort(arr: number[]): number[] {
  if (arr.length <= 1) return arr;
  const mid = arr.length / 2;
  const left = mergeSort(arr.slice(0, mid));    // 独立
  const right = mergeSort(arr.slice(mid));       // 独立
  return merge(left, right);
}

// DP - 子问题重叠
function fib(n: number, memo: number[] = []): number {
  if (n <= 1) return n;
  if (memo[n]) return memo[n];                    // 复用
  memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
  return memo[n];
}

DP vs 贪心

相同点

  • 都是求最优解
  • 都利用最优子结构

不同点

维度贪心动态规划
选择局部最优全局最优
回溯不回溯可能回溯
最优证明通常难一般可证
时间复杂度通常 O(n)通常 O(n²) 或更高

贪心失效的例子

typescript
// 硬币找零 - 贪心失效
const coins = [1, 3, 4];
// 贪心: 6 = 4 + 1 + 1 = 3枚
// 最优: 6 = 3 + 3 = 2枚

function greedyCoin(coins: number[], amount: number): number {
  let count = 0;
  let remaining = amount;

  // 从大到小选
  for (const coin of [...coins].sort((a, b) => b - a)) {
    while (remaining >= coin) {
      remaining -= coin;
      count++;
    }
  }

  return count;
}

// DP - 正确
function dpCoin(coins: number[], amount: number): number {
  const dp = Array(amount + 1).fill(Infinity);
  dp[0] = 0;

  for (let i = 1; i <= amount; i++) {
    for (const coin of coins) {
      if (i >= coin) {
        dp[i] = Math.min(dp[i], dp[i - coin] + 1);
      }
    }
  }

  return dp[amount];
}

何时用贪心

贪心适用条件

  • 问题具有贪心选择性质
  • 最优子结构
  • 可以证明局部最优 → 全局最优

DP vs DFS + 记忆化

本质相同

typescript
// 方法1: 自顶向下记忆化
function dfsWithMemo(n: number, memo: number[]): number {
  if (n <= 1) return n;
  if (memo[n]) return memo[n];
  return memo[n] = dfsWithMemo(n - 1, memo) + dfsWithMemo(n - 2, memo);
}

// 方法2: 自底向上 DP
function dpBottomUp(n: number): number {
  const dp = Array(n + 1).fill(0);
  dp[1] = 1;

  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }

  return dp[n];
}

// 方法3: 空间优化
function dpOptimized(n: number): number {
  let prev2 = 0, prev1 = 1;
  for (let i = 2; i <= n; i++) {
    [prev2, prev1] = [prev1, prev2 + prev1];
  }
  return prev1;
}

区别

维度DFS+记忆化自底向上 DP
方向递归(栈溢出风险)迭代
顺序自然顺序需要确定顺序
初始化延迟显式
调试容易较难

DP vs BFS(最短路)

相同点

  • 都可以用于最短路径问题

不同点

维度BFSDP
图类型无权图/有权图DAG(通常)
状态转移更复杂
队列需要不需要

BFS 更适合的场景

typescript
// 网格最短路径 - BFS
function shortestPath(grid: number[][], start: [number, number], end: [number, number]): number {
  const queue: [number, number, number][] = [[...start, 0]];
  const visited = new Set();

  while (queue.length > 0) {
    const [x, y, dist] = queue.shift()!;

    if (x === end[0] && y === end[1]) return dist;

    const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];
    for (const [dx, dy] of dirs) {
      const nx = x + dx, ny = y + dy;
      if (valid(nx, ny) && !visited.has(`${nx},${ny}`)) {
        visited.add(`${nx},${ny}`);
        queue.push([nx, ny, dist + 1]);
      }
    }
  }

  return -1;
}

// DP 更适合:有权图、有环、状态复杂

DP vs 线性代数

递推 → 矩阵加速

typescript
// 斐波那契的线性递推
// F(n) = F(n-1) + F(n-2)

// 矩阵形式:
// [F(n), F(n-1)]ᵀ = M × [F(n-1), F(n-2)]ᵀ
// M = [[1, 1], [1, 0]]

function fibMatrix(n: number): number {
  if (n <= 1) return n;

  const M = [[1, 1], [1, 0]];

  // 矩阵快速幂
  function matMul(A: number[][], B: number[][]): number[][] {
    const n = A.length;
    const C: number[][] = Array.from({ length: n }, () => Array(n).fill(0));
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        for (let k = 0; k < n; k++) {
          C[i][j] += A[i][k] * B[k][j];
        }
      }
    }
    return C;
  }

  function matPow(M: number[][], k: number): number[][] {
    const n = M.length;
    let result: number[][] = Array.from({ length: n }, (_, i) =>
      Array(n).fill(0).map((_, j) => i === j ? 1 : 0)
    );
    let base = M;

    while (k > 0) {
      if (k & 1) result = matMul(result, base);
      base = matMul(base, base);
      k >>= 1;
    }

    return result;
  }

  const R = matPow(M, n - 1);
  return R[0][0]; // F(n)
}

选择决策树


复杂度对照表

算法时间复杂度空间复杂度适用场景
暴力递归O(2^n)O(n)
记忆化O(n) ~ O(n²)O(n)树形DP
一维DPO(n) ~ O(n²)O(1) ~ O(n)序列DP
二维DPO(n×m)O(n×m) 或 O(n)网格DP
区间DPO(n³)O(n²)合并类
状态压缩O(n × 2^n)O(2^n)集合DP
矩阵加速O(n³ log k)O(n²)线性递推

参见: **DP算法索引** | **DP优化**

Knowledge4J — Java 知识库