mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-15 08:26:06 +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,26 +1,26 @@
|
||||
# 二分探索による挿入
|
||||
# 二分探索の挿入位置
|
||||
|
||||
二分探索は目標要素を探索するだけでなく、目標要素の挿入位置を探索するなど、多くの変種問題を解決するためにも使用されます。
|
||||
二分探索は目標要素の検索だけでなく、目標要素の挿入位置を探すなど、多くの派生問題の解決にも利用できます。
|
||||
|
||||
## 重複要素がない場合
|
||||
|
||||
!!! question
|
||||
|
||||
一意の要素を持つ長さ$n$のソート済み配列`nums`と要素`target`が与えられ、ソート順を維持しながら`target`を`nums`に挿入します。`target`が配列にすでに存在する場合は、既存の要素の左側に挿入します。挿入後の配列における`target`のインデックスを返してください。下図に示す例を参照してください。
|
||||
長さ $n$ の整列済み配列 `nums` と要素 `target` が与えられます。配列には重複要素は存在しません。ここで `target` を配列 `nums` に挿入し、その順序を保ちます。配列中にすでに要素 `target` が存在する場合は、その左側に挿入します。挿入後の配列における `target` のインデックスを返してください。例を以下の図に示します。
|
||||
|
||||

|
||||

|
||||
|
||||
前のセクションの二分探索コードを再利用したい場合、以下の2つの質問に答える必要があります。
|
||||
前節の二分探索コードを再利用したい場合は、次の二つの問題に答える必要があります。
|
||||
|
||||
**質問1**:配列にすでに`target`が含まれている場合、挿入位置は既存要素のインデックスになりますか?
|
||||
**問題 1**:配列に `target` が含まれる場合、挿入位置のインデックスはその要素のインデックスですか?
|
||||
|
||||
`target`を等しい要素の左側に挿入するという要件は、新しく挿入される`target`が元の`target`の位置を置き換えることを意味します。つまり、**配列に`target`が含まれている場合、挿入位置は確かにその`target`のインデックスです**。
|
||||
問題では `target` を等しい要素の左側に挿入するよう求めているため、新しく挿入された `target` は元の `target` の位置に入ります。つまり、**配列に `target` が含まれる場合、挿入位置のインデックスはその `target` のインデックスです**。
|
||||
|
||||
**質問2**:配列に`target`が含まれていない場合、どのインデックスに挿入されますか?
|
||||
**問題 2**:配列に `target` が存在しない場合、挿入位置はどの要素のインデックスですか?
|
||||
|
||||
二分探索プロセスをさらに考えてみましょう:`nums[m] < target`のとき、ポインタ$i$が移動します。これは、ポインタ$i$が`target`以上の要素に近づいていることを意味します。同様に、ポインタ$j$は常に`target`以下の要素に近づいています。
|
||||
二分探索の過程をさらに考えると、`nums[m] < target` のときは $i$ が移動します。これは、ポインタ $i$ が `target` 以上の要素へ近づいていることを意味します。同様に、ポインタ $j$ は常に `target` 以下の要素へ近づいています。
|
||||
|
||||
したがって、二分の終了時には確実に:$i$は`target`より大きい最初の要素を指し、$j$は`target`より小さい最初の要素を指します。**配列に`target`が含まれていない場合、挿入位置は$i$であることは明らかです**。コードは以下の通りです:
|
||||
したがって二分探索の終了時には、$i$ は最初の `target` より大きい要素を指し、$j$ は最初の `target` より小さい要素を指します。**よって、配列に `target` が含まれない場合、挿入インデックスは $i$ です**。コードは次のとおりです:
|
||||
|
||||
```src
|
||||
[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple}
|
||||
@@ -30,28 +30,28 @@
|
||||
|
||||
!!! question
|
||||
|
||||
前の質問に基づいて、配列に重複要素が含まれている可能性があると仮定し、他はすべて同じとします。
|
||||
前問を踏まえ、配列には重複要素が含まれる可能性があるものとし、それ以外の条件は変わりません。
|
||||
|
||||
配列に`target`の複数の出現がある場合、通常の二分探索は`target`の1つの出現のインデックスのみを返すことができ、**その位置の左右に`target`の出現がいくつあるかを特定することはできません**。
|
||||
配列中に複数の `target` が存在する場合、通常の二分探索ではそのうち一つの `target` のインデックスしか返せず、**その要素の左側と右側にあといくつ `target` があるかは分かりません**。
|
||||
|
||||
問題では目標要素を最も左の位置に挿入することが要求されているため、**配列内の最も左の`target`のインデックスを見つける必要があります**。最初に下図に示すステップを通してこれを実装することを考えてみましょう。
|
||||
問題では目標要素を最も左に挿入する必要があるため、**配列中で最も左にある `target` のインデックスを探す必要があります**。まずは以下の図に示す手順で実現することを考えます。
|
||||
|
||||
1. 二分探索を実行して`target`の任意のインデックス、例えば$k$を見つけます。
|
||||
2. インデックス$k$から開始して、最も左の`target`の出現が見つかるまで左に線形探索を行い、このインデックスを返します。
|
||||
1. 二分探索を実行し、任意の `target` のインデックスを得て、これを $k$ とします。
|
||||
2. インデックス $k$ から始めて左へ線形探索し、最も左の `target` を見つけたら返します。
|
||||
|
||||

|
||||

|
||||
|
||||
この方法は実現可能ですが、線形探索を含むため、時間計算量は$O(n)$です。この方法は、配列に多くの重複する`target`が含まれている場合に非効率です。
|
||||
この方法は使用できますが、線形探索を含むため、時間計算量は $O(n)$ です。配列中に重複した `target` が多い場合、この方法の効率は低くなります。
|
||||
|
||||
今度は二分探索コードを拡張することを考えてみましょう。下図に示すように、全体的なプロセスは同じままです。各ラウンドで、まず中間インデックス$m$を計算し、次に`target`と`nums[m]`の値を比較して、以下のケースになります。
|
||||
次に、二分探索のコードを拡張することを考えます。以下の図に示すように、全体の流れは変えず、各反復でまず中点インデックス $m$ を計算し、その後 `target` と `nums[m]` の大小関係を判定して、次のいくつかの状況に分けます。
|
||||
|
||||
- `nums[m] < target`または`nums[m] > target`のとき、これは`target`がまだ見つかっていないことを意味するため、通常の二分探索を使用して探索範囲を狭め、**ポインタ$i$と$j$を`target`に近づけます**。
|
||||
- `nums[m] == target`のとき、これは`target`より小さい要素が範囲$[i, m - 1]$にあることを示すため、$j = m - 1$を使用して範囲を狭め、**ポインタ$j$を`target`より小さい要素に近づけます**。
|
||||
- `nums[m] < target` または `nums[m] > target` のときは、まだ `target` を見つけていないことを意味するため、通常の二分探索と同じ区間縮小を行い、**ポインタ $i$ と $j$ を `target` に近づけます**。
|
||||
- `nums[m] == target` のときは、`target` より小さい要素が区間 $[i, m - 1]$ にあることを意味するため、$j = m - 1$ として区間を縮小し、**ポインタ $j$ を `target` より小さい要素に近づけます**。
|
||||
|
||||
ループ後、$i$は最も左の`target`を指し、$j$は`target`より小さい最初の要素を指すため、**インデックス$i$が挿入位置です**。
|
||||
ループ終了後、$i$ は最も左の `target` を指し、$j$ は最初の `target` より小さい要素を指すため、**インデックス $i$ が挿入位置です**。
|
||||
|
||||
=== "<1>"
|
||||

|
||||

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

|
||||
@@ -74,9 +74,9 @@
|
||||
=== "<8>"
|
||||

|
||||
|
||||
以下のコードを観察してください。分岐`nums[m] > target`と`nums[m] == target`の操作は同じであるため、これら2つの分岐をマージできます。
|
||||
以下のコードを観察すると、分岐 `nums[m] > target` と `nums[m] == target` の処理は同じであるため、両者はまとめることができます。
|
||||
|
||||
それでも、ロジックがより明確になり、可読性が向上するため、条件を展開したままにしておくことができます。
|
||||
それでも、判定条件を分けたままにしておくことは可能であり、そのほうがロジックがより明確で、可読性も高くなります。
|
||||
|
||||
```src
|
||||
[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion}
|
||||
@@ -84,8 +84,8 @@
|
||||
|
||||
!!! tip
|
||||
|
||||
このセクションのコードは「閉区間」を使用しています。「左閉右開」に興味がある場合は、自分でコードを実装してみてください。
|
||||
本節のコードはすべて「両閉区間」の書き方です。興味のある読者は「左閉右開」の書き方を自分で実装してみてください。
|
||||
|
||||
要約すると、二分探索は本質的にポインタ$i$と$j$の探索目標を設定することです。これらの目標は特定の要素(`target`など)または要素の範囲(`target`より小さいものなど)である可能性があります。
|
||||
要するに、二分探索とはポインタ $i$ と $j$ にそれぞれ探索目標を設定することにほかなりません。目標は具体的な要素(たとえば `target`)である場合もあれば、要素の範囲(たとえば `target` より小さい要素)である場合もあります。
|
||||
|
||||
二分探索の連続ループにおいて、ポインタ$i$と$j$は段階的に事前定義された目標に近づきます。最終的に、それらは答えを見つけるか、境界を越えた後に停止します。
|
||||
繰り返される二分のループの中で、ポインタ $i$ と $j$ はどちらも事前に定めた目標へ徐々に近づいていきます。最終的に、それらは答えを見つけるか、境界を越えたところで停止します。
|
||||
|
||||
Reference in New Issue
Block a user