mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-06 12:44:19 +00:00
Re-translate the Japanese version (#1871)
* Retranslate Japanese docs with GPT-5.4 * Retranslate Japanese code with GPT-5.4
This commit is contained in:
@@ -1,80 +1,80 @@
|
||||
# 編集距離問題
|
||||
|
||||
編集距離は、レーベンシュタイン距離とも呼ばれ、一つの文字列を別の文字列に変換するために必要な最小修正回数を指し、情報検索や自然言語処理で2つのシーケンス間の類似度を測定するためによく使用されます。
|
||||
編集距離は、Levenshtein 距離とも呼ばれ、2つの文字列の相互変換に必要な最小の編集回数を指し、通常は情報検索や自然言語処理において2つの系列の類似度を測るために用いられます。
|
||||
|
||||
!!! question
|
||||
|
||||
2つの文字列 $s$ と $t$ が与えられたとき、$s$ を $t$ に変換するために必要な最小編集回数を返してください。
|
||||
2つの文字列 $s$ と $t$ を入力し、$s$ を $t$ に変換するのに必要な最小編集回数を返してください。
|
||||
|
||||
1つの文字列に対して3種類の編集操作を行えます。1文字の挿入、1文字の削除、任意の文字への置換です。
|
||||
|
||||
文字列に対して3種類の編集を実行できます:文字の挿入、文字の削除、または文字を他の任意の文字に置換。
|
||||
下図に示すように、`kitten` を `sitting` に変換するには 3 回の編集が必要で、内訳は 2 回の置換と 1 回の挿入です。`hello` を `algo` に変換する場合も 3 回必要で、内訳は 2 回の置換と 1 回の削除です。
|
||||
|
||||
下の図に示すように、`kitten` を `sitting` に変換するには3回の編集が必要で、2回の置換と1回の挿入を含みます。`hello` を `algo` に変換するには3ステップが必要で、2回の置換と1回の削除を含みます。
|
||||

|
||||
|
||||

|
||||
**編集距離問題は決定木モデルで自然に説明できます**。文字列が木のノードに対応し、1回の決定(1回の編集操作)が木の1本の辺に対応します。
|
||||
|
||||
**編集距離問題は決定木モデルで自然に説明できます**。文字列は木のノードに対応し、1ラウンドの決定(編集操作)は木のエッジに対応します。
|
||||
下図に示すように、操作に制限がない場合、各ノードからは多くの辺を派生でき、それぞれの辺が1種類の操作に対応します。これは `hello` から `algo` への変換に多くの経路があり得ることを意味します。
|
||||
|
||||
下の図に示すように、操作に制限がない場合、各ノードは多くのエッジを導出でき、それぞれが1つの操作に対応するため、`hello` を `algo` に変換する可能な経路は多数あります。
|
||||
決定木の観点から見ると、本問の目標はノード `hello` とノード `algo` の間の最短経路を求めることです。
|
||||
|
||||
決定木の観点から、この問題の目標は、ノード `hello` とノード `algo` の間の最短経路を見つけることです。
|
||||

|
||||
|
||||

|
||||
### 動的計画法の考え方
|
||||
|
||||
### 動的プログラミングアプローチ
|
||||
**第1ステップ:各ラウンドの決定を考え、状態を定義して、$dp$ テーブルを得る**
|
||||
|
||||
**ステップ1:各ラウンドの決定を考え、状態を定義し、それにより $dp$ テーブルを得る**
|
||||
各ラウンドの決定は、文字列 $s$ に対して1回の編集操作を行うことです。
|
||||
|
||||
各ラウンドの決定は、文字列 $s$ に対して1つの編集操作を実行することを含みます。
|
||||
編集操作の過程で問題の規模が徐々に小さくなることを期待します。そうして初めて部分問題を構築できます。文字列 $s$ と $t$ の長さをそれぞれ $n$ と $m$ とし、まず両文字列の末尾の文字 $s[n-1]$ と $t[m-1]$ を考えます。
|
||||
|
||||
編集プロセス中に問題のサイズを段階的に縮小することを目指し、これにより部分問題を構築できます。文字列 $s$ と $t$ の長さをそれぞれ $n$ と $m$ とします。まず、両方の文字列の末尾文字 $s[n-1]$ と $t[m-1]$ を考慮します。
|
||||
- $s[n-1]$ と $t[m-1]$ が同じなら、それらをスキップして、直接 $s[n-2]$ と $t[m-2]$ を考えます。
|
||||
- $s[n-1]$ と $t[m-1]$ が異なるなら、$s$ に対して1回の編集(挿入、削除、置換)を行い、両文字列の末尾の文字を同じにします。そうすることでそれらをスキップし、より小さい問題を考えられます。
|
||||
|
||||
- $s[n-1]$ と $t[m-1]$ が同じ場合、それらをスキップして直接 $s[n-2]$ と $t[m-2]$ を考慮できます。
|
||||
- $s[n-1]$ と $t[m-1]$ が異なる場合、$s$ に対して1つの編集(挿入、削除、置換)を実行して、2つの文字列の末尾文字を一致させ、それらをスキップしてより小規模な問題を考慮できるようにする必要があります。
|
||||
つまり、文字列 $s$ に対する各ラウンドの決定(編集操作)は、$s$ と $t$ における残りの未一致文字を変化させます。したがって、状態は現在 $s$ と $t$ で考えている第 $i$ と第 $j$ 文字とし、$[i, j]$ と記します。
|
||||
|
||||
したがって、文字列 $s$ での各ラウンドの決定(編集操作)は、$s$ と $t$ でマッチされる残りの文字を変更します。したがって、状態は $s$ と $t$ で現在考慮されている $i$ 番目と $j$ 番目の文字であり、$[i, j]$ と表記されます。
|
||||
状態 $[i, j]$ に対応する部分問題は、**$s$ の先頭 $i$ 文字を $t$ の先頭 $j$ 文字に変換するのに必要な最小編集回数**です。
|
||||
|
||||
状態 $[i, j]$ は部分問題に対応します:**$s$ の最初の $i$ 文字を $t$ の最初の $j$ 文字に変更するために必要な最小編集回数**。
|
||||
これにより、サイズが $(i+1) \times (j+1)$ の2次元 $dp$ テーブルが得られます。
|
||||
|
||||
これから、サイズ $(i+1) \times (j+1)$ の二次元 $dp$ テーブルを得ます。
|
||||
**第2ステップ:最適部分構造を見つけ、状態遷移方程式を導く**
|
||||
|
||||
**ステップ2:最適部分構造を特定し、状態遷移方程式を導出する**
|
||||
部分問題 $dp[i, j]$ を考えます。これに対応する2つの文字列の末尾文字は $s[i-1]$ と $t[j-1]$ であり、編集操作の違いに応じて下図の3つの場合に分けられます。
|
||||
|
||||
部分問題 $dp[i, j]$ を考慮すると、これに対応する2つの文字列の末尾文字は $s[i-1]$ と $t[j-1]$ であり、下の図に示すように3つのシナリオに分けることができます。
|
||||
|
||||
1. $s[i-1]$ の後に $t[j-1]$ を追加すると、残りの部分問題は $dp[i, j-1]$ です。
|
||||
2. $s[i-1]$ を削除すると、残りの部分問題は $dp[i-1, j]$ です。
|
||||
3. $s[i-1]$ を $t[j-1]$ に置換すると、残りの部分問題は $dp[i-1, j-1]$ です。
|
||||
1. $s[i-1]$ の後ろに $t[j-1]$ を追加する。このとき残る部分問題は $dp[i, j-1]$ です。
|
||||
2. $s[i-1]$ を削除する。このとき残る部分問題は $dp[i-1, j]$ です。
|
||||
3. $s[i-1]$ を $t[j-1]$ に置き換える。このとき残る部分問題は $dp[i-1, j-1]$ です。
|
||||
|
||||

|
||||
|
||||
上記の分析に基づいて、最適部分構造を決定できます:$dp[i, j]$ の最小編集回数は、$dp[i, j-1]$、$dp[i-1, j]$、$dp[i-1, j-1]$ の中の最小値に編集ステップ $1$ を加えたものです。対応する状態遷移方程式は:
|
||||
以上の分析から、最適部分構造は次のように得られます。$dp[i, j]$ の最小編集回数は、$dp[i, j-1]$、$dp[i-1, j]$、$dp[i-1, j-1]$ の3つのうち最小の編集回数に、今回の編集回数 $1$ を加えたものです。対応する状態遷移方程式は次のとおりです:
|
||||
|
||||
$$
|
||||
dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1
|
||||
$$
|
||||
|
||||
注意してください。**$s[i-1]$ と $t[j-1]$ が同じ場合、現在の文字に対して編集は必要ありません**。この場合、状態遷移方程式は:
|
||||
注意すべき点として、**$s[i-1]$ と $t[j-1]$ が同じ場合、現在の文字を編集する必要はありません**。この場合の状態遷移方程式は次のとおりです:
|
||||
|
||||
$$
|
||||
dp[i, j] = dp[i-1, j-1]
|
||||
$$
|
||||
|
||||
**ステップ3:境界条件と状態遷移の順序を決定する**
|
||||
**第3ステップ:境界条件と状態遷移の順序を決める**
|
||||
|
||||
両方の文字列が空の場合、編集回数は $0$ です。つまり、$dp[0, 0] = 0$ です。$s$ が空で $t$ が空でない場合、最小編集回数は $t$ の長さに等しく、つまり最初の行 $dp[0, j] = j$ です。$s$ が空でなく $t$ が空の場合、最小編集回数は $s$ の長さに等しく、つまり最初の列 $dp[i, 0] = i$ です。
|
||||
2つの文字列がともに空のとき、編集回数は $0$、すなわち $dp[0, 0] = 0$ です。$s$ が空で $t$ が空でないとき、最小編集回数は $t$ の長さに等しいため、先頭行は $dp[0, j] = j$ です。$s$ が空でなく $t$ が空のとき、最小編集回数は $s$ の長さに等しいため、先頭列は $dp[i, 0] = i$ です。
|
||||
|
||||
状態遷移方程式を観察すると、$dp[i, j]$ の解決は左、上、左上の解に依存するため、二重ループを使用して正しい順序で $dp$ テーブル全体を走査できます。
|
||||
状態遷移方程式を観察すると、$dp[i, j]$ の解は左、上、左上の解に依存します。そのため、2重ループで $dp$ テーブル全体を順方向に走査すれば十分です。
|
||||
|
||||
### コード実装
|
||||
### コードの実装
|
||||
|
||||
```src
|
||||
[file]{edit_distance}-[class]{}-[func]{edit_distance_dp}
|
||||
```
|
||||
|
||||
下の図に示すように、編集距離問題の状態遷移プロセスはナップサック問題と非常に似ており、二次元グリッドを埋めることと見なすことができます。
|
||||
下図に示すように、編集距離問題の状態遷移の過程はナップサック問題と非常によく似ており、どちらも2次元グリッドを埋めていく過程とみなせます。
|
||||
|
||||
=== "<1>"
|
||||

|
||||

|
||||
|
||||
=== "<2>"
|
||||

|
||||
@@ -120,9 +120,9 @@ $$
|
||||
|
||||
### 空間最適化
|
||||
|
||||
$dp[i, j]$ は上の $dp[i-1, j]$、左の $dp[i, j-1]$、左上の $dp[i-1, j-1]$ の解から導出され、直接走査では左上の解 $dp[i-1, j-1]$ が失われ、逆走査では事前に $dp[i, j-1]$ を構築できないため、どちらの走査順序も実行可能ではありません。
|
||||
$dp[i,j]$ は上の $dp[i-1, j]$、左の $dp[i, j-1]$、左上の $dp[i-1, j-1]$ から遷移されますが、順方向走査では左上の $dp[i-1, j-1]$ を失い、逆方向走査では $dp[i, j-1]$ を事前に構築できません。そのため、どちらの走査順序も適切ではありません。
|
||||
|
||||
この理由で、変数 `leftup` を使用して左上の $dp[i-1, j-1]$ からの解を一時的に保存し、左と上の解のみを考慮すればよくなります。この状況は無制限ナップサック問題と似ており、直接走査が可能です。コードは以下の通りです:
|
||||
そのため、変数 `leftup` を用いて左上の解 $dp[i-1, j-1]$ を一時保存し、左と上の解だけを考えればよくなります。このときの状況は完全ナップサック問題と同じであり、順方向走査を用いることができます。コードは次のとおりです:
|
||||
|
||||
```src
|
||||
[file]{edit_distance}-[class]{}-[func]{edit_distance_dp_comp}
|
||||
|
||||
Reference in New Issue
Block a user