ほぼすべてのバイナリ検索が簡単であることを私の周りの人々が、それは本当に本当?バイナリ検索は、実際には非常に簡単ですか?単純ではありません。クヌースはどのように言って兄(発明のKMPアルゴリズム)を見て:
バイナリサーチの基本的な考え方は、比較的簡単ですが、詳細は意外に難しいことができます...
この文は理解することができます:アイデアは、悪魔の内容は非常に簡単です。
、番号を探している境界線の右側を探して、境界線の左側を探す:本稿では、最も一般的に使用されるバイナリサーチシーンのいくつかを探索します。
また、当社は、このようなwhileループはミッドプラス1かどうか、不平等の等号を持参してください、そしてのでそれが必要かどうかなど、細部に入るためにしています。これらの違いについて詳細にこれらの違いとその理由を分析すると、あなたは正確に正しいバイナリサーチアルゴリズムを記述するための柔軟性を確保することができます。
見つけるために、1つ、2つのサブフレーム
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = (right + left) / 2;
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
二分探索技術の分析は次のとおりです。他には表示されませんが、すべての場合に明確であれば、他に書くことが、あなたは明確にすべての詳細を表示することができます。この記事では、それを明確に、読者が簡略化され、自分のを理解できるようにすることを目的とし、それ以外の場合に使用します。
あなたがバイナリコードが見えます見ると...発生する可能性のある詳細の一部の場所をマーク。、すべての最初は、これらの場所に注意を払います。これらの場所の例とその後の分析は、変更のいずれかの種類を持つことができます。
加えて、中間を計算するとき、それはオーバーフローを防止するために熟練を必要と書かれた推奨事項を宣言する:MIDは= +左(右-左)/ 2、この資料では、この問題を無視します。
第二に、番号(基本的なバイナリサーチ)を探してください
このシナリオは最も簡単で、そしておそらく最も身近な存在する場合それは、番号を検索し、そうでない場合は、そのインデックスを返します-1。
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意
while(left <= right) { // 注意
int mid = (right + left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意
else if (nums[mid] > target)
right = mid - 1; // 注意
}
return -1;
}
1なぜwhileループの条件は、<=の代わりに<ですか?
:割り当てが初期化されているので、右nums.length - 1は、それはむしろnums.lengthよりも、インデックスの最後の要素です。
これは、2つの異なるバイナリ検索機能で発生することがあり、差がある:両端に対応前者は[右、左]間隔閉じ、閉じた左右の開区間[左、右)に対応する、インデックスサイズNUMSため.LENGTHが範囲外です。
我々は、[左、右]間隔の両端で閉じ、このアルゴリズムである使用します。このセクションでは、我々は同様に「探索空間」(サーチスペース)と呼ばれるかもしれない、各検索する部分です。
ときに私はそれを探して停止する必要がありますか?もちろん、あなたがターゲット終端を見つけることができるとき。
if(nums[mid] == target)
return mid;
見つからない場合でも、あなたはwhileループを終了する必要があり、その後、-1を返します。ときにそれをループが終了されるべきですか?終了しなければならない探索空間は空であるあなたが見てする必要はありません、との事を見つけることを意味するものではありませんでしたことを意味します。
(左<=右)終了条件が残っている間に==右+ 1、[右、右+ 1]セクションの形式で記述されるか、または[3,2]、表示に特定の番号を持つこと、この検索時間間隔空の、いずれかがないため、図3および以下2バールに等しいです。だから、ループ終了が正確である一方で、この時間は、それが直接返すことができます-1。
(左<右)終了条件が残っている間に==右、間隔は、フォーム[右、右]に書かれている、又は特定数にテープ[2,2]、この検索時間間隔非エンプティ、および数2、しかし、ループが終了している間、この時間。これは、区間[2,2]は、インデックス2が検索されていない、欠落していることを意味し、この時間は直接-1を返し、エラーが発生する可能性がある場合。
もちろん、あなたが(右<左)ながら使用する必要があることができれば、我々はすでに、エラーの原因を知って、良いパッチを打ちます:
//...
while(left < right) {
// ...
}
return nums[left] == target ? left : -1;
2。なぜ左=ミッド+ 1、右=中旬- 1?私はいくつかのコード右=半ばまたは左=半ば、これらの単純な計算のいずれも、最終的には問題、どのようにどのように判断するを参照してください?
:これは見つけるのが難しいの二分法ですが、限り、あなたは上記を理解し、それを判断するのは非常に簡単にすることができます。
ただ、「探索空間」という概念をクリアし、探索空間のアルゴリズムは、両端で閉じられ、それは[左、右]です。私たちが見つけたときので、インデックスが半ば探索空間の次のステップにそれをどのように決定するか、ターゲットを意味しますか?
右、または[ミッド+ 1、右] - もちろん、[1左、中央]を検索するためにありますか?半ば検索されているので、検索区間から削除する必要があります。
3。このアルゴリズムは、どのような欠陥を持っていますか?
A:この時点で、あなたはすでに、すべてのアルゴリズムの詳細、およびこのような治療のための理由を知っている必要があります。しかし、この方法には制限があります。
たとえば、あなたが規則配列NUMS = [1,2,2,2,3]、ターゲット= 2を与え、このアルゴリズムは、インデックスが2である、そうです返します。私は左のボーダーターゲットを取得したい場合でも、すなわち1つのインデックス、または私は右の境界目標、すなわちインデックス3を取得したいので、このアルゴリズムは、処理することはできません。
このような要件が共通しています。あなたは、言う目標指数を見つけ、その後、左または右の線形探索を行うことはありませんでしょうか?はい、ではなく、時間の二分探索の複雑なログを保証することは困難であるため。
我々は見つけるために、これらの2つのアルゴリズムのバイナリアルゴリズムのフォローアップについて説明します。
第三には、バイナリサーチの左の境界を探します
詳細のマークであるコード、で直接見ては注意を払う必要があります:
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意
while (left < right) { // 注意
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid; // 注意
}
}
return left;
}
1.なぜ、代わりに<=の(<右、左)ながら?
:初期右= nums.length代わりnums.lengthため、同じ分析方法 - 1。したがって、「サーチスペース」の各サイクルは、[)、右、左左及び開閉する権利。
(左<右)ながらコンディション終了が==右、左、区間[左を検索この時間)が空であることを起こる残っていた、それは正しく終了することができます。
2.なぜ運用-1を返しませんでしたか?この値のターゲットがNUMSに存在しない場合、どのように?
:あなたがステップバイステップに行きたいので、この「左ボーダー」は、任意の特別な意味を持っているかを理解するには:
この配列のために、このアルゴリズムは1を返します。この解釈の意味は、1:NUMS要素2は、1以下です。
例えば、規則配列NUMSの= [2,3,5,7-]、目標= 1、アルゴリズムは意味、0を返す:NUMS素子1が0未満を有します。8が4未満を持っているNUMS要素をターゲット= 8ならば、アルゴリズムは意味、4を返します。
要約では、関数の戻り値(すなわち、左の変数の値)値の間隔が閉区間[0、nums.length]で見ることができるので、我々は、単に-1を返すために正しい時にコードの2行を追加します。
while (left < right) {
//...
}
// target 比所有数都大
if (left == nums.length) return -1;
// 类似之前算法的处理方式
return nums[left] == target ? left : -1;
3.なぜ左=ミッド+ 1、右=中旬?そして、アルゴリズムの前に同じではありませんか?
:これは非常に良い説明で、私たちの「探索空間」が[検出された後、左と開閉する権利、そのときNUMS [中期])右、左されているので、次のステップは、ミッドサーチスペースを削除する必要があり、すなわち、2つのセクションに分割され[左、中)または[ミッド+ 1、右)。
4.なぜアルゴリズムは国境の左側を検索することができますか?
:キーは、この場合の[中間] ==ターゲット処理NUMSです。
if (nums[mid] == target)
right = mid;
ターゲットを見つけるためにすぐに戻るには、しかし狭い上限右「探索空間は、」左区間[、半ば)で検索を継続しないとき可視、それは左、国境の左側をロックすることを目的に縮小しています。
5.なぜ、左の代わりに、右に戻りますか?
:しばらく終了条件は、右==残っているので、左右が同じで返します。
第四に、バイナリサーチの右端に探して
コード右の境界と同じの左の境界を見つけるために探して、唯一の2つの違いは、マークされました:
int right_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
left = mid + 1; // 注意
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left - 1; // 注意
1.なぜ、このアルゴリズムは、右の境界を見つけることができますか?
A:同様に、重要な点はここにあります:
if (nums[mid] == target) {
left = mid + 1;
ときNUMS [中期] ==ターゲット、すぐに戻りますが、境界線の右側をロックする権利、目的を縮小間隔を作り、「探索空間」の下限を高めるために残されていません。
2.なぜ最終的には左返さなかった- 1ではなく左境界の機能よりも、左返しますか?そして、私は境界線の右側の検索があるので、右の息子を返すべきだと感じています。
:まず、whileループの終了条件は、左右が同じになるよう==右、あなたが戻り、右の右の特性を反映する必要が残されている - 1をよく。
特別な点探索右の境界線である理由の減少、この判断に重要な条件としては:
if (nums[mid] == target) {
left = mid + 1;
// 这样想: mid = left - 1
我々は左=ミッド+ 1に委ねなければならない、すなわち、whileループの終わりに言うことである更新から、NUMS [左] 確かに等しくない標的、NUMSながら[左- 1] とすることができるターゲット。
左のアップデートが=ミッド+ 1を左にしなければならない理由として、国境検索の左側で、それらを繰り返しません。
3.なぜ運用-1を返しませんでしたか?この値のターゲットがNUMSに存在しない場合、どのように?
[nums.length、0]の範囲内である右、それが残っている==終了条件が残っているのでながら以前の検索は、境界線を左に似て、適切に、コードの2行を追加することが可能である-1。
while (left < right) {
// ...
}
if (left == 0) return -1;
return nums[left-1] == target ? (left-1) : -1;
第五に、締結
詳細にこれらの違いの原因と結果を整理した最初のロジック:
まず、最も基本的なバイナリサーチアルゴリズム:
因为我们初始化 right = nums.length - 1
所以决定了我们的「搜索区间」是 [left, right]
所以决定了 while (left <= right)
同时也决定了 left = mid+1 和 right = mid-1
因为我们只需找到一个 target 的索引即可
所以当 nums[mid] == target 时可以立即返回
第二に、バイナリサーチの左の境界線を探します。
因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid+1 和 right = mid
因为我们需找到 target 的最左侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧右侧边界以锁定左侧边界
第三个,寻找右侧边界的二分查找:
因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid+1 和 right = mid
因为我们需找到 target 的最右侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧左侧边界以锁定右侧边界
又因为收紧左侧边界时必须 left = mid + 1
所以最后无论返回 left 还是 right,必须减一
如果以上内容你都能理解,那么恭喜你,二分查找算法的细节不过如此。
通过本文,你学会了:
1. 分析二分查找代码时,不要出现 else,全部展开成 else if 方便理解。
2. 注意「搜索区间」和 while 的终止条件,如果存在漏掉的元素,记得在最后检查。
3. 如需要搜索左右边界,只要在 nums[mid] == target 时做修改即可。搜索右侧时需要减一。
就算遇到其他的二分查找变形,运用这几点技巧,也能保证你写出正确的代码。LeetCode Explore 中有二分查找的专项练习,其中提供了三种不同的代码模板,现在你再去看看,很容易就知道这几个模板的实现原理了。