{}const=>[]async()letfn</>var
開発アルゴリズム

初心者向け動的プログラミング: 「これは一体何?」から面接の問題解決まで

PythonとJSの例を使用した動的プログラミングのわかりやすい説明。5つの古典的なタスク、ステップバイステップの解決方法。

К

Kodik

著者

4分で読める

シンプルな言葉での動的プログラミングとは何ですか?

5の階乗を計算していると想像してみてください。

そのためには、5 × 4 × 3 × 2 × 1が必要です。今度は6の階乗を計算するように求められます。すべてを再計算するのは愚かなことですよね?5! = 120であることはすでに知っているので、6を掛けるだけです。

動的プログラミング(DP) これはまったく同じアプローチです。複雑な問題を解決するために、それをサブタスクに分割し、 結果を記憶します同じものを2回カウントしないようにします。

DPタスクの主な特徴:

🎯 最適なサブ構造

大きな課題を解決するには、小さなサブタスクを解決することから始まります

🔄 重複するサブタスク

同じサブタスクが何度も発生する

⚡ 最適なものを見つける必要があります

最大、最小、または方法の数

🔥 10万人以上の学生が参加中

理論を読むのに疲れた?
コーディングの時間だ!

Kodik — 実践でプログラミングを学ぶアプリ。AIメンター、インタラクティブなレッスン、実際のプロジェクト。

🤖 AI 24時間
🎓 修了証
💰 無料
🚀 始める
今日参加

DP への 2 つのアプローチ:メモリ化とタブ化

1. 記憶(上から下へ)

これは再帰と結果のキャッシュです。大きなタスクから始めて、基本的なケースに移ります。

def fibonacci_memo(n, memo={}):
    # 基本ケース
    if n <= 1:
        return n
    
    # キャッシュをチェックしています
    if n in memo:
        return memo[n]
    
    # 計算して保存する
    memo[n] = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo)
    return memo[n]

print(fibonacci_memo(50))  # 即時!

使用するタイミング: タスクのロジックが再帰を通じて直感的に理解できる場合。

2. タブ(下から上へ)

基本的なケースから最終的な答えまで、解決策の表を作成します。再帰はありません!

def fibonacci_table(n):
    if n <= 1:
        return n
    
    # 表を作成する
    dp = [0] * (n + 1)
    dp[0] = 0
    dp[1] = 1
    
    # 下から上に記入します
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    
    return dp[n]

print(fibonacci_table(50))

使用するタイミング: 最大のパフォーマンスとメモリ制御が必要な場合。

2026年に知っておくべきDPの古典的な課題

タスク1:フィボナッチ数

DPなしの難易度: O(2ⁿ)は指数関数です!
DPの難易度: O(n)は線形です!

上記の解決策はすでに見てきました。これはスタートに最適なタスクです。

タスク 2: ナップサック問題

あなたはW容量のバックパックを持った強盗です。重さと価値のあるアイテムがあります。どのようにして最大の価値を得ることができますか?

def knapsack(weights, values, capacity):
    n = len(weights)
    # dp [i] [w] = i 個のアイテムと容量 w での最大値
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]
    
    for i in range(1, n + 1):
        for w in range(1, capacity + 1):
            # アイテムを取らない
            dp[i][w] = dp[i-1][w]
            
            # 収まる場合はアイテムを取る
            if weights[i-1] <= w:
                dp[i][w] = max(
                    dp[i][w],
                    dp[i-1][w - weights[i-1]] + values[i-1]
                )
    
    return dp[n][capacity]

weights = [1, 2, 3, 5]
values = [10, 5, 15, 7]
capacity = 7

print(knapsack(weights, values, capacity))  # 32

実際の適用: リソースの割り当て、予算計画、サーバー負荷の最適化。

タスク3:最長共通部分列(LCS)

2つの文字列の最長の共通部分列を見つけます。git diffの基礎!

def lcs(text1, text2):
    m, n = len(text1), len(text2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if text1[i-1] == text2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    
    return dp[m][n]

print(lcs("ABCDGH", "AEDFHR"))  # 3 (ADH)

用途: バージョン管理システム、剽窃チェック、バイオインフォマティクス(DNA比較)。

タスク4:コインチェンジ

異なる通貨単位のコインがあります。どれだけの方法で金額を集めることができますか?

def coin_change(coins, amount):
    # dp [i] = iの合計に対する最小コイン数
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0  # 0の金額には0コインが必要です
    
    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i:
                dp[i] = min(dp[i], dp[i - coin] + 1)
    
    return dp[amount] if dp[amount] != float('inf') else -1

coins = [1, 2, 5]
amount = 11print(coin_change(coins, amount))  # 3 (5+5+1)

用途: フィンテックアプリケーション、POSシステム、トランザクションの最適化。

タスク 5: Edit Distance (レーベンシュタイン距離)

1つの行を別の行に変換するための最小操作数。

def edit_distance(word1, word2):
    m, n = len(word1), len(word2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    # 初期化
    for i in range(m + 1):
        dp[i][0] = i
    for j in range(n + 1):
        dp[0][j] = j
    
    # 表の記入
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if word1[i-1] == word2[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = 1 + min(
                    dp[i-1][j],    # 削除
                    dp[i][j-1],    # 挿入
                    dp[i-1][j-1]   # 交換
                )
    
    return dp[m][n]

print(edit_distance("kitten", "sitting"))  # 3

用途: タイプミスの修正、検索エンジン、オートコンプリート。

DPの問題を解くためのステップバイステップの方法論

ステップ1:再帰的な解決策を見つける

まずは、最適化を考えずに、再帰で問題を解いてください。

# 最適化されていないバージョンdef fib (n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

ステップ2:メモリを追加する

結果を保存するための辞書を追加します。

def fib_memo(n, memo={}):
    if n <= 1:
        return n
    if n not in memo:
        memo[n] = fib_memo(n-1, memo) + fib_memo(n-2, memo)
    return memo[n]

ステップ3:タブに変換する(オプション)

配列を使用して反復アプローチに切り替えます。

def fib_table(n):
    if n <= 1:
        return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

ステップ4:メモリを最適化する

多くの場合、O(n)の代わりにO(1)のメモリを使用できます。

def fib_optimized(n):
    if n <= 1:
        return n
    prev2, prev1 = 0, 1
    for _ in range(2, n + 1):
        current = prev1 + prev2
        prev2, prev1 = prev1, current
    return prev1

2026年のDPの主要なパターン

1D DP

1 次元配列

  • フィボナッチ数

  • Climbing Stairs

  • House Robber

2D DP

2次元配列

  • Longest Common Subsequence

  • Edit Distance

  • Knapsack Problem

行のDP

テキストの処理

  • Palindrome Subsequences

  • String Matching

  • Wildcard Matching

木の上のDP

ツリー構造

  • Binary Tree Maximum Path Sum

  • Diameter of Binary Tree

グラフ上の最短経路

グラフアルゴリズム

  • Shortest Path (Bellman-Ford)

  • Traveling Salesman Problem

初心者がよくする間違い

ミス 1: 基本的なケースを忘れる

def fib(n):
    return fib(n-1) + fib(n-2)
# 無限の再帰!

正解

def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

エラー2:テーブルサイズが正しくありません

dp = [0] * n
# インデックス0..nが必要な場合、
# n+1のサイズが必要です!

正解

dp = [0] * (n + 1)

ミス 3: 境界線を確認しない

if dp[i-1]:
# i = 0の場合はどうなりますか?

正解

if i > 0 and dp[i-1]:

面接のための実用的なヒント。

  1. 紙に表を描く — 視覚化は依存関係を見つけるのに役立ちます

  2. 小さな例から始めましょう — fib(0), fib(1), fib(2)...

  3. 再発の公式を探す — dp[i]は以前の値にどのように依存しますか?

  4. 論理を声に出して話す - これはあなたの思考の流れを示しています

  5. 最初は最適な解決策を書くことを恐れないでください — 後で改善できます

Web開発者向けのJavaScriptバージョン。

多くの初心者はJavaScriptを使用しているので、JSの例を次に示します。

// メモ化function fibMemo(n, memo = {}) {
    if (n <= 1) return n;
    if (memo[n]) return memo[n];
    
    memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
    return memo[n];
}

// タブ関数fibTable (n ){
    if (n <= 1) return n;
    
    const dp = new 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];
}

// 最適化されたバージョン (O(1) メモリ)function fibOptimized(n) {
    if (n <= 1) return n;
    
    let prev2 = 0, prev1 = 1;
    
    for (let i = 2; i <= n; i++) {
        const current = prev1 + prev2;
        prev2 = prev1;
        prev1 = current;
    }
    
    return prev1;
}

console.log(fibOptimized(50)); // 12586269025

結論

動的プログラミングは魔法ではなく、問題解決への論理的なアプローチです。重要なポイント:

  1. サブタスクに分割する — 繰り返しパターンを見つける

  2. 結果を保存する — 重複してカウントしないでください

  3. 再帰から始めましょう — 次に最適化する

  4. 定期的に練習する — DPには練習が必要

動的プログラミングを習得すると、あなたは:

✅ほとんどの技術面接に合格する

✅ 本番環境での実際のタスクを最適化できる

✅多くのライブラリやフレームワークの内部構造を理解する

✅ アルゴリズム的な考え方を学ぶ

覚えておいてください: どんなに優れた開発者にとっても、すべてのアルゴリズムはかつては複雑に思えたものです。重要なのは練習と忍耐です。

動的プログラミングやその他の多くの重要なトピックは、コーディックで学ぶことができます — 開発者向けの実践的なコースを提供する教育プラットフォーム。私たちは、面接や仕事で本当に役立つコンテンツを作成しています!

📱 さらに、私たちには素晴らしい テレグラムチャンネル フレンドリーなコミュニティで、

  • 面接の課題を分析する

  • 役立つ記事を共有します

  • お互いの成長を助け合う

  • 開発の最新トレンドについて話し合う

コーディックに移動 Telegramに参加する

一緒に成長する開発者のコミュニティに参加しましょう! 💪

🎯先延ばしをやめよう

記事は気に入った?
実践の時間だ!

Kodikでは読むだけでなく、すぐにコードを書く。理論 + 実践 = 本当のスキル。

即座に実践
🧠AIがコードを説明
🏆修了証

登録不要 • カード不要