LeetCodeは、ソートされた配列内の要素の最初と最後の位置を検索します
@author:Jingdai
@date:2020.11.09
トピックの説明(34の質問)
昇順の整数配列
nums
とターゲット値が与えられますtarget
。配列内の指定されたターゲット値の開始位置と終了位置を見つけます。アルゴリズムの時間計算量はO(log n)レベルである必要があります。
ターゲット値が配列に存在しない場合は、を返し
[-1, -1]
ます。
サンプル入力
nums = [5,7,7,8,8,10], target = 8
サンプル出力
[3,4]
アイデア
この質問は典型的な二分探索の問題ですが、これは最も基本的な二分探索の問題とは少し異なります。つまり、検索された番号が繰り返される可能性がありますが、一般的な二分探索は直接検索して返すことですが、ここではできません。見つけた値が最初、最後、または中間のいずれであるかわからないため、直接戻ります。
基本的な2進数のルックアップが繰り返されないため、一般的には、
while(left <= right)
裁判官が直接返品を見つけた場合、返品が見つからない場合に使用し-1
ます。ここでは、少し改善する必要があります。周期的に決定されたものwhile(left <= right)
を使用して、while(left < right)
循環するために本明細書では使用されない。
while(left <= right)
ループアウトを使用すると、解がループから外れてはならない場合、left > right
区間の[left, right]
長さは負になります。使用されているwhile(left < right)
ループのうち、時間、left
及びright
間隔、同じであってもよい[left, right]
長さが1であり、そしてそれは可解場合left
とright
必ずしも同じで、溶液はこの時点ですleft
。次に、このトピックに戻り、トピックを2つの部分に分割して、要素の最初の位置と最後の位置を見つけます。まず、検索要素の最初の位置を見つける方法、つまり検索間隔を半分にする方法を見てください。
target == nums[middle]
:現時点では、これが必ずしも最初の要素ではないため、直接返すことはできません。範囲を狭めるにはどうすればよいですか。最初の要素、最初の要素、それともそれmiddle
、またはmiddle
前を見つけるために、middle
後ろのセクションは無視できます。さあright = middle
。target > nums[middle]
:これは基本的な二分探索と同じleft = middle + 1
です。target < nums[middle]
:しましょうright = middle - 1
。最初の要素の位置を見つけるコードスニペット全体を見てください。
public int findFisrtIndex(int[] nums, int target) { if (nums == null || nums.length == 0) return -1; if (nums.length == 1) return nums[0] == target ? 0 : -1; int left = 0; int right = nums.length - 1; int middle; while (left < right) { middle = left + (right - left) / 2; if (target == nums[middle]) { right = middle; } else if (target > nums[middle]) { left = middle + 1; } else { right = middle - 1; } } if (nums[left] == target) return left; return -1; }
次のステップは、最後の要素の位置を見つけることです。これは基本的に最初のアイデアと同じなので、説明はせず、後ろのコード部分を見てください。
ここでもう少し詳しく説明します。バイナリ検索を作成するときに、間隔が正しく分割されていない場合、無限ループが発生しやすくなります。つまり、要素が2つあるときに間隔を短縮できない場合、次のようになります。無限ループ。最後の要素を見つけるためのコードを書くときは
middle
、選択方法を切り上げる必要があります。最初の要素の位置を見つけるように切り下げることはできません。そうしないと、無限ループが発生します。自分で2つの要素に対して試してください。間違いを犯しやすいこと。
コード
public int[] searchRange(int[] nums, int target) { int firstIndex = findFisrtIndex(nums, target); if (firstIndex == -1) return new int[] { -1, -1}; int lastIndex = findLastIndex(nums, target); return new int[] { firstIndex, lastIndex}; } public int findFisrtIndex(int[] nums, int target) { if (nums == null || nums.length == 0) return -1; if (nums.length == 1) return nums[0] == target ? 0 : -1; int left = 0; int right = nums.length - 1; int middle; while (left < right) { middle = left + (right - left) / 2; if (target == nums[middle]) { right = middle; } else if (target > nums[middle]) { left = middle + 1; } else { right = middle - 1; } } if (nums[left] == target) return left; return -1; } public int findLastIndex(int[] nums, int target) { if (nums == null || nums.length == 0) return -1; if (nums.length == 1) return nums[0] == target ? 0 : -1; int left = 0; int right = nums.length - 1; int middle; while (left < right) { middle = left + (right - left + 1) / 2; if (target == nums[middle]) { left = middle; } else if (target > nums[middle]) { left = middle + 1; } else { right = middle - 1; } } if (nums[left] == target) { return left; } return -1; }
概要
最後に、この二分探索の概要を少し書きます。
- 最も基本的な二分探索には
while (left <= right)
、この行を使用します。問題は少し複雑で、while (left < right)
より簡単に解決できます。- 二分探索の場合、範囲を絞り込んで、各ステップでの範囲の狭さ(タイトル
nums[middle]
やtarget
等号など)を明確にします。- 区間除算の場合、要素が2つ残っていると無限ループが発生しやすいので、無限ループになるかどうかわからない場合は、2つの要素の状況を試して、修正が必要かどうかを確認してください。 。切り上げ。