動的計画法

問題を部分問題に分割し、結果をメモ化して重複計算を排除するアルゴリズム設計手法

アルゴリズム最適化

動的計画法とは

動的計画法 (Dynamic Programming, DP) は、問題を重複する部分問題に分割し、各部分問題の結果をメモ化 (キャッシュ) して重複計算を排除するアルゴリズム設計手法である。再帰的な解法の指数的な計算量を多項式時間に削減する。

フィボナッチ数列の例

// ❌ 素朴な再帰: O(2^n) - 同じ計算を何度も繰り返す
function fib(n: number): number {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}

// ✅ メモ化 (トップダウン): O(n)
function fibMemo(n: number, memo = new Map<number, number>()): number {
  if (n <= 1) return n;
  if (memo.has(n)) return memo.get(n)!;
  const result = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
  memo.set(n, result);
  return result;
}

// ✅ テーブル (ボトムアップ): O(n)、スタックオーバーフローなし
function fibDP(n: number): number {
  if (n <= 1) return n;
  let prev = 0, curr = 1;
  for (let i = 2; i <= n; i++) [prev, curr] = [curr, prev + curr];
  return curr;
}

トップダウン vs ボトムアップ

アプローチ 方法 メリット
トップダウン (メモ化) 再帰 + キャッシュ 直感的、必要な部分問題のみ計算
ボトムアップ (テーブル) ループ + 配列 スタックオーバーフローなし

DP が適用できる条件

DP が適用できるのは、最適解が部分問題の最適解から構成される「最適部分構造」と、同じ部分問題が複数回出現する「重複部分問題」の 2 つの条件を満たす場合だ。

代表的な DP 問題

問題 説明
ナップサック問題 容量制限内で価値を最大化
最長共通部分列 (LCS) 2 つの文字列の共通部分列
コイン問題 最小枚数で金額を作る
編集距離 文字列の変換に必要な最小操作数

コイン問題

// 金額 amount を作るのに必要な最小コイン枚数
function coinChange(coins: number[], amount: number): number {
  const dp = new Array(amount + 1).fill(Infinity);
  dp[0] = 0;
  for (let i = 1; i <= amount; i++) {
    for (const coin of coins) {
      if (coin <= i) dp[i] = Math.min(dp[i], dp[i - coin] + 1);
    }
  }
  return dp[amount] === Infinity ? -1 : dp[amount];
}

coinChange([1, 5, 10], 13); // 4 (10 + 1 + 1 + 1)

実務での DP

コーディング面接では頻出だが、実務で DP を直接書く機会は少ない。ただし、キャッシュ (メモ化) の考え方は API レスポンスのキャッシュ、計算結果のキャッシュなど広く応用される。

実践的な知識は関連書籍でも得られる。

関連用語