34個の質問の「電源ボタン」:最初と最後の位置にソートされた配列内の要素を探します
この質問は、規則的な配列に私たちを必要とnums
してターゲット要素を見つけるために、target
最初と最後の位置の登場。ターゲット要素は、規則的な配列に表示されない場合は、それぞれの開始位置と終了位置を考慮-1
して-1
。
方法の一つ:(ブルートフォース)を解決するために暴力
一つの方法は暴力である、我々は唯一の配列いったん開始から終了までに横断する必要がある解決策を考えるのは簡単です、我々は、第1の位置と要素が表示され、ターゲットの最後の位置を見つけることができます。問題の意味によると、私たちは最も一般的な場合は、数は厳密に小さい最初の出会いよりもあると思いますtarget
し、出会いの数が等しいtarget
遭遇した最後の数は、より厳密に大きいですtarget
。
- 我々はトラバースを開始することができそうだとすれば、要素が等しい横断するためにチェックする
target
だけで遭遇したに等しく、target
現在の位置を記録する際に、 - その後、要素が等しくないかどうかを確認するためにトラバース、トラバースを行った
target
だけで出会いに等しくないtarget
現在の場所に以前の位置を記録する場合。
このアルゴリズムの時間複雑性はあります 、対象者の要件を満たしていません。ここでは、コードを見てください。
Javaコード:
public class Solution {
public int[] searchRange(int[] nums, int target) {
int[] targetRange = new int[]{-1, -1};
int len = nums.length;
if (len == 0) {
return targetRange;
}
for (int i = 0; i < len; i++) {
if (nums[i] == target) {
targetRange[0] = i;
break;
}
}
// 连第 1 个位置都没有找到,说明已经遍历完整个数组了
if (targetRange[0] == -1) {
return targetRange;
}
for (int i = targetRange[0] + 1; i < len; i++) {
if (nums[i] != target) {
targetRange[1] = i - 1;
break;
}
}
if (targetRange[1] == -1) {
targetRange[1] = len - 1;
}
return targetRange;
}
}
説明:
-
第2要素整数配列を作成
targetRange
、初期値[-1, -1]
、配列の長さが変数に割り当てられ、len
配列の長さに等しい、0
時間、直接リターンしますtargetRange
。 -
次いで、標識からの
0
場所を横断する、限り見つけることと等価としてtarget
の要素を、それがあろうtargetRange
添字0
に割り当てられ、その要素のi
、その後ループを抜けます。 -
ここで注意すべきことの一つは、次のとおりです。全体のトラバーサルの過程で、あれば
targetRange[0]
再割り当てされていない、それがターゲット要素ことを示してtarget
規則的な配列ではnums
、遺跡がされますが存在しないtargetRange
に戻りました。 -
その後、我々は、最初に出現する位置から要素を標的とする
targetRange[0]
検出要素が等しくない限り、次の位置を通過target
、その以前の位置を記録しますtargetRange[1]
に割り当てられi - 1
、そしてその後、ループを終了することができます。 -
最後の位置にあるターゲット要素は、単にアレイを注文した場合は、最終的な判断を下すことができますので、実際には、ループの本体は、単純に実行する方法はありません、細部にも注意を払う、あれば
targetRange[1]
まだ割り当てられていない、それを置きます配列の最後の位置に割り当てられています。
方法2:バイナリ検索
ここでは、注文した配列内のターゲット要素の開始位置と終了位置を見つけるためにバイナリ検索を使用する方法を見て。
-
中間位置の範囲の要素の値の範囲を見てバイナリ検索の基本的な考え方は、
nums[mid]
対象要素target
の大小関係、及び、その後に入る目標値のどの部分かを決定します。 -
この質問のために、二分法の共通の問題との最大の違いは、ターゲット要素があることがわかり
target
、より整然と配列内に存在する可能性があります。 -
そして、私たちは見て要素法の中間位置でのバイナリ検索値を使用する場合
nums[mid]
、正確にターゲット要素に等しいtarget
時にそれを見つけるために継続する必要があるが、今回は比較的容易に検索直線性誤差に分類し、正しいアプローチは、バイナリ検索を続けることです。
-
もちろん、ターゲット要素の位置は、最初の時間は、厳密に未満にはできません表示される必要があります
target
要素の位置、暴力の前に分析ソリューションに応じて、ターゲット要素の位置は、第一には、厳密未満で現れるtarget
要素の境界位置、私たちこの二分法の境界はこのアイデアを使用して見つけることができます。 -
対称的に、1がより厳密に大きくすることはできません最後に現れなければならないターゲット要素の位置
target
の要素の位置は、ターゲット要素1の最後に出現する位置をより厳密に大きいですtarget
私たちは二分法により、このアイデアを使用することができますので、要素の境界位置境界を見つけます。
ここでは、コードを見て:
Javaコード:
public class Solution {
public int[] searchRange(int[] nums, int target) {
int len = nums.length;
if (len == 0) {
return new int[]{-1, -1};
}
int firstPosition = findFirstPosition(nums, target);
if (firstPosition == -1) {
return new int[]{-1, -1};
}
int lastPosition = findLastPosition(nums, target);
return new int[]{firstPosition, lastPosition};
}
private int findFirstPosition(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = (left + right) >>> 1;
// 小于一定不是解
if (nums[mid] < target) {
// 下一轮搜索区间是 [mid + 1, right]
left = mid + 1;
} else {
right = mid;
}
}
if (nums[left] == target) {
return left;
}
return -1;
}
private int findLastPosition(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = (left + right + 1) >>> 1;
// 大于一定不是解
if (nums[mid] > target) {
// 下一轮搜索区间是 [left, mid - 1]
right = mid - 1;
} else {
left = mid;
}
}
return left;
}
}
-
最初は、まだ配列の可変長に割り当てています
len
。 -
その後、特判断を下す次の場合、配列の長さ
0
時に直接リターン[-1 , -1]
。 -
次に、我々は前半見つける
target
最初に現れる位置を、我々はパッケージプライベートメソッドにそれを置きますfindFirstPosition
:- 左の境界初期化
left = 0
および右の境界線をright = len - 1
、検索する範囲の左および右の境界線を表します。 - 次常にバイナリ検索対象要素、我々はサイクル条件書かれ続けることができ
while (left < right)
、ここで書かれたノートが厳密に小さい、これは比較的一般的な文言であるよりもある、そのアイデアがあり、最終的な利点について判断を下すために要素の要件を満たすためにありますサイクルタイムが終了しなければならないleft == right
設定を戻す必要があり、我々は最終的には考えていないleft
か、リターンright
の。しかし、このようなアプローチは、ノートにいくつかの場所を持って、我々はすぐに言及します。 - その後、コードは、被写体の中間位置で計算されます
int mid = left + (right - left) / 2;
- 分析によると、ちょうどその時
nums[mid] < target
、時間、mid
同様にmid
左のすべての要素は間違いないtarget
位置の最初に出現したので、次のラウンドの要素は、私たちは、検索になり[mid + 1, right]
、そのため、左マージンが設定されていますmid + 1
。 - 次は
nums[mid] < target
反対の、nums[mid] >= target
この時間間隔を検索がされてif
いるサーチスペースのこのブランチの反対[left, mid]
、我々は検証する必要があります:私たちはより厳しく、より多く表示された場合はtarget
、target
この番号の左にある特定の位置が表示されますが最初に出現します。我々は正確に等しい数が表示された場合target
、位置の最初の発生が、そこにもいる可能性がありtarget
、それの左にある最初の位置を生じるが、これは正しい位置に、サーチ区間の次のラウンドは、そうではないということ[left, mid]
問題は、あなたは右の境界を設定する必要がありませんがright = mid
。 - そこに次のターゲット要素であるとき、ループを終了するには
left
(またはあるright
今回は、その値に等しい)タイトルが言うので、ターゲット要素は、配列には存在しないかもしれませんが、見ていないので、必要性だけでは再びそれを行うには判決は、このステップは、後処理と呼ばれています。 - 場合
nums[left] == target
添え字位置がleft
あるtarget
別段の最初の発生の位置-1
。
- 左の境界初期化
-
対称的に、我々は、書き込みに見える
target
位置の最後に出現するコード。-
まず、検索であれば、ことは確かである
target
時間の最初の出現の位置、我々は見つけることができませんでしたtarget
検索では、target
最後に出現する位置、我々は確かにこの番号を見つけることができませんので、あなたは特別な裁判官を行うことができます; -
findLastPosition
構造とfindFirstPosition
我々が直接ダウンコピーし、同じように、あなたは変更する必要があることif
と、else
それ以前の分析に基づいて、ロジック、nums[mid] > target
時間、mid
およびmid
右側のすべての要素は間違いないがtarget
、彼らが表示され、最後の1、その上の検索の次の要素一定で[left, mid - 1]
、従って、右境界セットmid - 1
。 -
次は
nums[mid] > target
反対の、nums[mid] <= target
間隔を検索この時間がif
ある探索空間のこのブランチの反対[mid, right]
、我々は検証する必要があります:私たちは厳密に未満番号が表示された場合はtarget
、target
この番号の右側にある最後の特定の位置が表示され登場しました。我々は正確に等しい数が表示されている場合はtarget
、それが最後の位置が、そこにもように見えることがありtarget
、その右の位置にある最後の1を表示されますが、この位置に残されてはならない、そのため、インターバルの次のラウンドのために検索することです[mid, right]
、その後、あなたは、左マージンを設定する必要がある問題はありませんleft = mid
。 -
特別な注意を払うために、この時間、このです:あなたが表示されたら
left = mid
とright = mid - 1
二分探索収縮挙動にこの境界、我々はいくつかの小さな調整を行う、多数の真ん中に時間を取る必要があり、この括弧内に、リガ、1:int mid = left + (right - left + 1) / 2;
-
その理由はこうである。
/
すなわち、デフォルトの丸め動作が切り捨てされていることを、整数の除算です(3 + 4) / 2 = 3
。私たちは、元の文言を使用する場合は、mid
取得するために失敗することはありませんright
、との境界が縮小されるleft = mid
とright = mid - 1
、時間検索範囲が2つだけの要素であることをするときの中間に正しい数を取得するために失敗することはありませんから、私たちは、このようそれの図を描きますこのコードは真ん中、無限ループに、コードを考慮しています左の境界線または右端のいずれかへの分岐を実行したら、私たちは、この時点では、その間隔切っても切れないを発見しました。 -
この問題を解決するために、実際に私は言っている、私たちは、次の整数の除算、すなわち、括弧リガに、丸めに行動変容を丸めたときに中央の数字を取る必要があり
1
、この丸め動作の変更は最後だけ必要とするが、時間の残りの2つの要素が作るために、私たちはただ全体的に全体のテイクが問題ではありませんしましょう。 -
この1は経験の要約ですが、また徐々にまとめの使用の人々が、実際には、このプロセスを見つけるのは難しいことではありません、我々は唯一の無限ループの時手続にプリントアウトする必要がある
left
、right
とmid
の値、それがあります問題を特定し、解決策を考えるのは簡単。 -
そこに次の要素の対象がまだ存在する場合、ループを出るに
left
(またはであるright
それらの値が同じで今回)が表示されませんでした。しかし、我々は実際には、コードをここで実行することができ、ことに注意してください、それは、ということではありませんtarget
でnums
、特定の存在、私たちの以前の判決を見てみましょう。したがって、我々は、裁判官に必要がないnums[left]
かどうか等しくないtarget
場合、left
値は必然であるtarget
整然と配列のnums
最後の1つのインデックスに登場します。
-
デバッグコード:
Javaコード:
class Solution {
public int[] searchRange(int[] nums, int target) {
int len = nums.length;
if (len == 0) {
return new int[]{-1, -1};
}
int firstPosition = findFirstPosition(nums, target, len);
if (firstPosition == -1) {
return new int[]{-1, -1};
}
int lastPosition = findLastPosition(nums, target, len);
return new int[]{firstPosition, lastPosition};
}
private int findLastPosition(int[] nums, int target, int len) {
int left = 0;
int right = len - 1;
while (left < right) {
int mid = left + (right - left) / 2;
System.out.println("left = " + left + ", mid = " + mid + ", right = " + right);
// 严格大于 target 的时候不是解
if (nums[mid] > target) {
// 下一轮搜索的区间是 [left, mid - 1]
System.out.println("下一轮搜索的区间是 [left, mid - 1]");
right = mid - 1;
} else {
System.out.println("下一轮搜索的区间是 [mid, right]");
// [mid, right]
left = mid;
}
}
return left;
}
private int findFirstPosition(int[] nums, int target, int len) {
int left = 0;
int right = len - 1;
while (left < right) {
int mid = left + (right - left) / 2;
// 严格小于 target 的时候不是解
if (nums[mid] < target) {
// 下一轮搜索的区间是 [mid + 1, right]
left = mid + 1;
} else {
right = mid;
}
}
if (nums[left] == target) {
return left;
}
return -1;
}
}