提供する剣のポイント 21. 奇数が偶数より前になるように配列の順序を調整します。
-
衝突ポインタ 。 左から最初の偶数を探し続け、右から最初の奇数を探し続け 、見つかったら 2 つの位置を 交換します。
この質問は[905. 配列を奇数と偶数でソートする]とほぼ同じです。
剣はオファー 57 を指しています。合計は 2 つの数字です。
この問題は「 167. 2つの数の和 II - 入力順序配列」と同じ問題です。
-
1) ポインタを衝突させ 、 和(L + R) を計算し、 ターゲット との関係を判断して 、左ポインタを動かすか右ポインタを動かすかを決定します。
-
2) 二分探索 、最初に nums[i]を固定し、次に 残りの配列 [ i+1...end ]内の target = s - nums[i] の値を 二分探索します。
-
注: 質問の配列は 昇順でソートされ ているため、上記の 2 つのメソッドを使用でき、解決策が見つかった限りそれを返すことができます。
26. 順序付けられた配列内の重複を削除する
-
1) 高速ポインタと低速ポインタ 、 低速は 処理済み領域の最後の位置 を指し 、高速は未処理領域の最初の位置を指します、初期は低速 = 0、高速 = 1 、継続的に高速に逆方向に移動します
-
fast の値が slow の値と 等しくない 場合 、 slow++は fastの値をslowの 位置に置き 、最後にslow + 1 を配列長として返します。
-
注: この質問ができる理由は、質問の配列が 昇順に配置されている ためです。
このアルゴリズムは、未処理領域の値と処理済み領域の最後の値を継続的に比較し、昇順に配列されているため、処理済み領域の最後の値と同じ値をスキップするだけで済みます。次に、異なる値を前に置くだけです。
-
2) 高速ポインタと低速ポインタ 。 低速は 処理済み領域の次の位置 を指し 、高速は未処理領域の最初の位置を指します。最初は低速 = 1、高速 = 1 で、引き続き高速に後方に移動します。
-
fast の値が fast-1 と異なる 場合は、 fast の値を throw 位置に配置し、次に slow++ を 配置し、 最後に 配列 lengthとして throw を返します。
-
注: この質問ができる理由は、質問の配列が 昇順に配置されている ためです。
このアルゴリズムは、未処理領域の現在値と前回の値を継続的に比較し、昇順に配列されているため、前回と同じ値をスキップするだけで済みます。次に、異なる値を前に置くだけです。
質問:低速と高速が位置1から始まるのはなぜですか?
これは、slowの意味が、処理された領域の次の位置であるためです。最初は、以前に予約された0 の位置は、その位置がデフォルトで処理された領域であることを示します。つまり、1 という数字自体は繰り返されないため、slow が開始されます。0位置から 次の位置から開始します。1の位置は最初の未処理の位置を表すため、 高速は1 の位置から開始します。方法 1 では、0 から 開始するスローの意味は同じで、0 の位置が処理された最後の位置 (および処理された最初の位置) であることを意味します。
80. 順序付き配列の重複を削除 II
-
26 と同様に、 slow は 処理される間隔の次の位置 を指します。slow の前に2 つの空席が確保されています。slowと fast は3番目の要素から開始します。つまり、最初はlow = 2、fast = 2で、後方に進み続けます。断食する。
-
fast と slow - 2 の位置の要素が異なる 場合は、 fast の値を slow の 位置に入れ、次に slow++ を入れ、 最後に 配列の長さとして slowを返します。
-
注: この質問ができるのは、質問の配列が 順序どおりに配置されている ためです。
ここで先頭に 2 つのスペースが確保されているのは、問題では繰り返される要素が 2 回だけ出現する必要があるため、最初の 2 つの要素が繰り返されても問題はなく、最初の 2 つの要素が異なっていても問題ありません。
もう 1 つの疑問は、なぜ 遅い - 2 の位置の数字が速い位置と比較されるのかということです。
fastがslow - 2 と同じであり、slow - 2 がslow - 1と同じである場合 、同じ数値が 3 つ存在する可能性があります: nums[fast] == nums[slow - 2] == nums[slow] - 1 ] 、この時点では、繰り返される要素の出現回数は> 2であるため、 fastは、 slow - 2とは異なる必要があり、繰り返し要素は最大 2 回出現できます。
では、 速いと遅い-1を 比較して、違う場合に代入しても大丈夫でしょうか?下の図に示すように、これにより、繰り返される数字に対するすべての答えが完全に失われます (繰り返される 2 と 3 のうち 1 つだけが保持されます)。
287. 重複する番号を見つける
-
1)高速ポインタと低速ポインタ. 繰り返される番号を持つ配列は、141 個の循環リンク リストに似たリングを形成します. リングへの入り口は、繰り返される要素です 。
-
質問で与えられた配列を array 形式のリンク リスト として扱います。配列の添え字は要素へのポインタであり、配列の要素もポインタとみなされます。
-
たとえば、 0は nums[0] を指すポインターであり 、nums[0]もnums[nums[0]]を指すポインターです。
-
Slow と Fast は最初は 0 で、 Slow は 1 ステップ、 Fast は 2 ステップかかります。slow が fast に追いついた場合は 、 ループ を 終了します。このとき、slow = 0として最初に戻り、その後、slowとfastを実行します。 low がfastに追いつく場合、ミーティング ポイントは繰り返し番号です。
質問で与えられた繰り返しの数字を含む配列がリングを形成する理由を見てみましょう。
上の図に示すように、関数f(x) = nums[x]を使用してシーケンスx、nums[x]、nums[nums[x]]、nums[nums[nums[x]]] を作成します。 ... ... つまり、各数値の位置は前の数値の値になります。x=nums[0] から開始すると、nums には繰り返しの数値があるため、必然的に次のようなシーケンスが形成されます。サイクル。
より複雑な例/大きな円:
ループに入る点が、探している繰り返し番号 (つまり、上の 2 つの例では 1 と 9) であることを確認するのは難しくありません。問題は、それをどのように見つけるかです。答えは、高速ポインタと低速ポインタを使用することです。
-
2 )二 分探索 、 [ 1 , n] 数値区間での 二 等分。
-
各分割後、配列内の 要素の数 ≤ Mid を数え、countと Midの関係に基づいて次にどちらの側を分割するかを決定します。
-
① count ≤ Mid の場合、左側に繰り返しがないことを意味し、左側の境界 L = Mid + 1 を縮小し、右側の半分の区間 [mid + 1, R] を検索します。
-
② count > Midの場合、 [L, Mid]に繰り返し要素があること を意味し、 R をMid - 1に縮小し、区間[L, Mid - 1]の左半分まで探索を続けますが、このとき、重複要素の可能性があるため、変数resを使用して削除された要素のMid値を記録する必要があります。最後にresに戻ります。
2 つの部分に分割するときに要素の数≤ Midを数える必要があるのはなぜですか?
簡単に言うと、ニンジン1本につきピットは1つ、ピットは中央と左側に 最大で中 程度 (非繰り返し要素が1~n個連続する場合)、繰り返し要素がある場合は中 を超える場合があります。穴。
以下に示すように、cnt 配列を考えます。cnt[i] は、i 以下の nums 内の要素の数を表します。
nums 配列に重複がないことを仮定すると、cnt[i] ≤ nums[i] 、つまり≤ midの数がmidを超えないことは明らかです。 of ≤ 3 は最大 3 ですが、繰り返される要素が 1 つの場合、この条件は満たされない可能性があります。
どうやらこれは番号の重複が原因のようです。
問題配列は順序性がありませんが、cnt配列は徐々に増加し単調であり、この問題の二分法的なアイデアは、cnt配列の特性を使用して間隔分割を決定することです。
-
3) ビット演算 。 全員のバイナリのi番目のビットにある1 の数の合計をnums で数え、それを xとして記録します。全員のバイナリの i 番目のビットにある1の数の合計を numsに記録します。 [ 1 - n]を計算し、それを yとして記録します。 x > yの場合、繰り返される数値の 2 進数のi番目の位置が1であることを意味するため、数値がもう 1 つ存在することになります。32ビット 2 進数のうち、 1が多い数字の答えの 2 進数を1 に設定するだけです。
このコードをより極端にしたい場合は、まず最大数 n の最上位ビット 1 の位置を見つけます。上位ビットはすべて 0 であるため、ループはこのビットのみを処理する必要があります。参照コード:
27. 要素を削除する
-
1) 高速ポインタと低速ポインタ 。高速ポインタと低速ポインタは 0から開始し、 高速ポインタを 常に移動させ 、高速ポインタが検出したvalに等しくない値を低速ポインタの位置に移動します。
このアプローチは、元の配列自体を使用して答えを結果配列として受け取るものとみなすことができます。
問題解決のアイデア:
-
2) 衝突ポインタ 。L ポインタが削除される要素val に遭遇したとき 、配列の最後の要素を使用してこの要素を上書きし、R--とします。
-
上書き後の L の新しい番号 がまだ削除対象の要素である可能性がありますが、問題はありません。次の while ループで引き続き判断および処理が行われますが、少なくとも配列の末尾から要素は削除されています。
-
L ポインターが val ではない要素に遭遇した 場合は、L++を使用します。
-
各数値を処理する必要があり、最終的に配列の長さとしてL が返されるため、 while ループの条件は L<=Rです。
283. ゼロの移動
-
1) 高速ポインタと低速ポインタ . 高速ポインタと低速ポインタは 0 から開始して高速ポインタを動かし続けます. 高速ポインタが 0 以外の数値に 遭遇した場合は 、高速ポインタと低速ポインタの要素値を交換するだけです。
-
2) 高速ポインタと低速ポインタ 、高速ポインタと低速ポインタは 0 から開始し、高速ポインタを移動し続けます。 高速ポインタが0 以外の数値に遭遇する と 、直前の低速位置を直接上書きし、その後、slow++ を実行します。すべてが終わったら、slow後の位置を 0に設定します。
-
3) 方法 2) 用に最適化された fast および low ポインタ: fast がゼロ以外の値に遭遇し、 slowを上書きした後、 fastがthrowと等しくない場合は、fast を直接0に設定してから、slow++ を設定します。
202. ハッピーナンバー
-
1) ハッシュ検出ループ 、 HashSet を使用して重複を決定します。各 whileループは、 n != 1 でsetに含まれていない 限り 、 nをset set に追加し、nをnの各桁に更新します。 .平方和。
-
ループを終了した後は、 nが 1 に等しいかどうかを返すだけです (ループから抜け出すときの状況は 2 つだけです。n が1になるか、セット set内に繰り返しがあり、これは循環があることを意味します)。
-
num の二乗和を 求める 手法: num % 10 で 1 桁を取り出し、二乗を計算して結果に加算するたびに、 num / 10 で num が 0 になって 停止する まで 1 桁を削除します 。
この問題は LeetCode では難易度 [Easy] となっていますが、最終的に 1 が得られるだけでなく、最終的には無限ループになる可能性があるという、観察が難しい状況があると思います。ここで、ループは最終的に繰り返しの数値に遭遇するか、無限に達するまでどんどん大きくなる可能性があります。言い換えれば、2 つの状況が考えられ、多くのオンライン コースで説明されているのは、繰り返しの数字を含むハッシュ テーブルを設定するというデフォルトの解決策にすぎません。
リコウ氏の公式説明を見てみましょう。
いくつかの例から始めましょう。7から始めましょう。次の数値は 49 (72 = 49 であるため)、次の数値は 97 (42 + 92 = 97 であるため) です。1 が得られるまでこのプロセスを繰り返します。1 が得られたので、7 が幸せな数字であることがわかり、関数はtrueを返す必要があります。
別の例として、116 から始めましょう。二乗和によって次の数値を繰り返し計算すると、最終的に 58 になり、さらに計算すると 58 に戻ります。すでに計算した数値に戻るので、ループがあることがわかり、したがって 1 に到達することは不可能です。したがって、116 の場合、関数はfalseを返す必要があります。
私たちの調査に基づいて、次の 3 つの可能性があると推測されます。
1. 最終的には 1 になります。
2. やがてサイクルに入ります。
3. 値はどんどん大きくなり、最終的には無限大に近づきます。
3 番目の状況は、検出して対処するのがより困難です。1 で終わるのではなく、大きくなり続けるとどうやってわかるのでしょうか? 各桁の最大の数の次の桁について注意深く考えることができます。
3 桁の数値の場合、243 を超えることはできません。これは、243 未満のループに陥るか、1 に低下することを意味します。4 桁以上の数値は、3 桁に低下するまで各ステップで 1 ビットが失われます。したがって、最悪の場合、アルゴリズムは 243 未満のすべての数値をループし、その後、すでに行っていたループに戻るか、1 に戻る可能性があることがわかります。しかし、それが無限に続くわけではないため、3 番目のオプションは除外します。
コード内で 3 番目のケースを処理する必要がない場合でも、処理する必要がない理由を正当化できるように、なぜそれが起こらないのかを理解する必要があります。
-
2) 高速および低速ポインター検出ループ 、低速ポインターと高速ポインターの初期値は n から始まります。各サイクルで、低速ポインターは 1 回計算および更新されます ( 低速 は 二乗和に更新されます)。高速ポインタは、 高速ポインタが1になるまで、または高速ポインタと低速ポインタが一致するまで、 2 回 計算および更新(高速更新) (そのビットの二乗の合計)され、ループが終了します。
-
最後に、 高速ポインタが 1 に等しいかどうかを返す だけです (高速ポインタと低速ポインタが等しいためにループが終了した場合、それはループが存在することを意味します)。
-
注: 高速ポインタは 最初に 1 に遭遇します。
上記のコードでは、do-whileループを使用するのが最善であることに注意してください 。そうしないと、slowとfast の両方がnに初期化されるため、ループが開始されたときにループを終了する可能性があります。最終的には 1 になるか、ループが出現する (繰り返し) ので、検出するwhile(true)無限ループを直接記述し、ループ本体に終了条件を記述することもできます。
高速ポインタと低速ポインタは、周期的な問題を検出するための古典的な方法です。
881.救命ボート
-
1) 振り分け + 衝突ポインタ 最小限の船が必要なので、できるだけ多くの人が 1 つの ボートに乗り、残りの人が 1 つのボートに乗る必要があるため、 最初に振り分けを行い 、次に 2 つの船から 2 つの船を選択します衝突ポインタが中央で終了します。各人の体重の合計が 制限値以下 の場合はボートに乗ることができます。2人の体重が 制限値を超える場合は 、重い人のみが一人でボートに乗ることができます。
-
サイクルの終わりに、 L == R の場合、残りの 1 人が自分でボートに乗ります。(ループを終了する理由はL > Rである可能性もあるので、最終的に戻るときに判断する必要があることに注意してください)
-
2) 並べ替え + 中央展開。 最初に並べ替えてから、最初の位置<= 制限 / 2を右から左に見つけて、この位置からダブル ポインタを設定して左右に同時に展開する 必要があります 。二人で同じ船に乗れる場所は、一人で残った人は一人と一隻しか乗れません。
-
特別ペナルティ 1: 全員の体重が制限体重の半分 ( > limit / 2 ) を超えた場合、1 人だけが 1 隻のボートに乗ることができ、 続行せずに people.lengthが直接返されます。 この時点で二人の体重の合計が 限界 を超えてしまうからだ。
-
特別判断 2: 最も重い人の体重が <= 制限 / 2 の場合、2 人の体重の合計が 制限 を超えないため、2 人でボートに乗って帰ることを直接手配できます。 (people.length + 1)/2 に直接変換します 。