シンプルな言葉での動的プログラミングとは何ですか?
5の階乗を計算していると想像してみてください。
そのためには、5 × 4 × 3 × 2 × 1が必要です。今度は6の階乗を計算するように求められます。すべてを再計算するのは愚かなことですよね?5! = 120であることはすでに知っているので、6を掛けるだけです。
動的プログラミング(DP) これはまったく同じアプローチです。複雑な問題を解決するために、それをサブタスクに分割し、 結果を記憶します同じものを2回カウントしないようにします。
DPタスクの主な特徴:
🎯 最適なサブ構造
大きな課題を解決するには、小さなサブタスクを解決することから始まります
🔄 重複するサブタスク
同じサブタスクが何度も発生する
⚡ 最適なものを見つける必要があります
最大、最小、または方法の数
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 prev12026年の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]:面接のための実用的なヒント。
紙に表を描く — 視覚化は依存関係を見つけるのに役立ちます
小さな例から始めましょう — fib(0), fib(1), fib(2)...
再発の公式を探す — dp[i]は以前の値にどのように依存しますか?
論理を声に出して話す - これはあなたの思考の流れを示しています
最初は最適な解決策を書くことを恐れないでください — 後で改善できます
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結論
動的プログラミングは魔法ではなく、問題解決への論理的なアプローチです。重要なポイント:
サブタスクに分割する — 繰り返しパターンを見つける
結果を保存する — 重複してカウントしないでください
再帰から始めましょう — 次に最適化する
定期的に練習する — DPには練習が必要
動的プログラミングを習得すると、あなたは:
✅ほとんどの技術面接に合格する
✅ 本番環境での実際のタスクを最適化できる
✅多くのライブラリやフレームワークの内部構造を理解する
✅ アルゴリズム的な考え方を学ぶ
覚えておいてください: どんなに優れた開発者にとっても、すべてのアルゴリズムはかつては複雑に思えたものです。重要なのは練習と忍耐です。
動的プログラミングやその他の多くの重要なトピックは、コーディックで学ぶことができます — 開発者向けの実践的なコースを提供する教育プラットフォーム。私たちは、面接や仕事で本当に役立つコンテンツを作成しています!
📱 さらに、私たちには素晴らしい テレグラムチャンネル フレンドリーなコミュニティで、
面接の課題を分析する
役立つ記事を共有します
お互いの成長を助け合う
開発の最新トレンドについて話し合う
一緒に成長する開発者のコミュニティに参加しましょう! 💪
