-
Notifications
You must be signed in to change notification settings - Fork 0
35. Search Insert Position #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| class Solution { | ||
| public: | ||
| int searchInsert(vector<int>& nums, int target) { | ||
| for (int i = 0; i < nums.size(); i++) { | ||
| if (nums[i] >= target) { | ||
| return i; | ||
| } | ||
| } | ||
| return nums.size(); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| ## ステップ1 | ||
| 制約にO(log n)とあったので思いついたのは、~~バイナリーサーチツリー~~を用いた | ||
| ※指摘があったため訂正(木構造は使っていない) | ||
|
|
||
| 探索した結果端に辿り着いた場合の処理に時間がかかった | ||
| acceptまで34分 | ||
| 制約がなければforループO(n)で実装していた | ||
|
|
||
| 時間計算量 | ||
| O (log n) | ||
| 空間計算量 | ||
| O(1) | ||
|
|
||
| ## ステップ2 | ||
| elseを使った方がスッキリしそう | ||
| ・forループバージョンでも念の為書いてみた。すごくシンプルに書けるしLeetCode上でもacceptされた | ||
| ・whileループバージョンでも書いてみた。 | ||
|
|
||
| 命名について | ||
| start endが個人的にしっくりきているが、right leftとどっちがいいのだろう | ||
| 調べていたhighとlowも出てきた。middleを使うならこれが英語的にしっくりきそう。 | ||
|
|
||
| left < right VS left <= rightはどちらも間違いではない | ||
| 要素数が偶数の場合、前者は2つの真ん中の左側、後者は右側がmiddleとなる | ||
| >From a readability perspective, it might be slightly better (in my opinion) to use the exclusive one in languages with 0-based arrays and either one in languages with 1-based arrays, in order to minimise the number of -1's in the code. An argument could also be made to just stick to a single version in all languages, as to not require that people understand both versions or get confused between the two. | ||
| チーム間で決めておくことなのかな。 | ||
| https://stackoverflow.com/questions/44231413/binary-search-using-start-end-vs-using-start-end | ||
|
|
||
| ## ステップ3 | ||
| **3回書き直しやりましょう、といっているのは、不自然なところや負荷の高いところは覚えられないからです。** | ||
|
|
||
| ## 他の方の解法 | ||
| 自分は再帰で書いたけど、わざわざ再帰を使わずwhileで処理可能 | ||
| https://github.com/Mike0121/LeetCode/pull/43 | ||
|
|
||
| >CPU 周りでぱっと目に付いた単語はこんな感じなのですが、 | ||
| パイプライン | ||
| スーパースカラー | ||
| マイクロアーキテクチャ | ||
| 命令セット | ||
| マイクロコード | ||
| レジスタ | ||
| 実行ユニット | ||
| キャッシュメモリ | ||
| アウトオブオーダー | ||
| マルチコア | ||
| 同時マルチスレッディング | ||
| スループット | ||
| 常識の範囲のようなので要チェック | ||
| https://github.com/Yoshiki-Iwasa/Arai60/pull/34 | ||
|
|
||
| >課題の制約でnums contains distinct values sorted in ascending order.とあるので問題ないですが、たとえばnums = [3, 3, 3, 3], target = 3の場合とかの答えが違うくないですか? | ||
| middleを使わないforループの探索だとこの左端が回答となってしまう。。。ので今回はたまたまacceptされただけか | ||
|
|
||
| >条件を満たすものを1つ探している場合、 | ||
| >条件を満たすもののうち左端を探している場合、 | ||
| >条件を満たすところと満たさないところの切れ目を探している場合(上とほぼ同じ)、 | ||
| >あと、閉区間か、開閉区間か、などがあって、これは、「条件を満たすもののうち左端を探している場合に満たすものを1つ見つかったら中止するコードがついた」 | ||
| この辺りの目線はなかった。 | ||
| 今回はたまたまnumsがユニークであったので問題が無かったが実際は要件定義必須 | ||
| https://github.com/fhiyo/leetcode/pull/42 | ||
| https://github.com/sakupan102/arai60-practice/pull/42 | ||
|
|
||
| ## Discordなど | ||
|
|
||
| middleについれleet codeの解説より | ||
| >If left + right is greater than the maximum int value 2^31 −1, it overflows to a negative value. In Java, it would trigger an exception of ArrayIndexOutOfBoundsException, and in C++ it causes an illegal write, which leads to memory corruption and unpredictable results. | ||
|
|
||
| ## step4.cpp | ||
| ### 閉区間 [start, end] | ||
| * 探索空間、初期値の設定 | ||
| start = 0、end = nums.size() - 1 | ||
|
|
||
| * ループ終了条件 | ||
| start == end | ||
|
|
||
| * 更新操作 | ||
| nums[middle] < targetがtrueの場合[middle + 1, end] | ||
| nums[middle] < targetがfalseの場合[start, middle] | ||
|
|
||
| ## 半開区間 [start, end) or (start, end] | ||
| [start, end)でstep5.cppに実装 | ||
| * 探索空間、初期値の設定 | ||
| start = 0, end = nums.size() | ||
|
|
||
| * ループ終了条件 | ||
| start >= end | ||
|
|
||
| * 更新操作 | ||
| nums[middle] < targetがtrueの場合[middle + 1, end] | ||
| nums[middle] < targetがfalseの場合[start, middle] | ||
| 形で覚えるのではなく、下記の考え方を理解する | ||
| https://github.com/Yoshiki-Iwasa/Arai60/pull/35/commits/f279dd98a68111954a02344b20b47512ebffafc4#r1699552857 | ||
|
|
||
|
|
||
| >二分探索を、 [false, false, false, ..., false, true, true, ture, ..., true] と並んだ配列があったとき、 false と true の境界の位置を求める問題、または一番左の true の位置を求める問題と捉えているか? | ||
| >位置を求めるにあたり、答えが含まれる範囲を狭めていく問題と捉えているか? | ||
| >範囲を考えるにあたり、閉区間・開区間・半開区間の違いを理解できているか? | ||
| >用いた区間の種類に対し、適切な初期値を、理由を理解したうえで、設定できるか? | ||
| >用いた区間の種類に対し、適切なループ不変条件を、理由を理解したうえで、設定できるか? | ||
| >用いた区間の種類に対し、範囲を狭めるためのロジックを、理由を理解したうえで、適切に記述できるか? | ||
| https://discord.com/channels/1084280443945353267/1196498607977799853/1269532028819476562 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| class Solution { | ||
| public: | ||
| int searchInsert(vector<int>& nums, int target) { | ||
| return SearchInsertIndex(0, nums.size() - 1, nums, target); | ||
| } | ||
|
|
||
| private: | ||
| int SearchInsertIndex(int start, int end, vector<int>& nums, int target) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これ、start と end はどういう変数であるかを意識していたらいいと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| // 端にいってしまった場合の処理 | ||
| if (start > end) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 区間を閉区間としてとらえているため、 start > end となることはないと思います。終了条件は start == end となると思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
の部分で、
一番書くべきことは
この書き方ですと、 start を mid に設定してはいけない理由が分かりませんでした。
最終的な挙動からボトムアップに思考しているように見え、違和感を感じました。二分探索の問題のモデルからトップダウンに考えていき、その内容を記述するのが良いと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nodchip
ここですが start <= middle < end のような書き方でもよろしいでしょうか?
指摘されるまで気づかなかったのですが、確かに答えから説明を作っていました。 頂いた指摘事項を元にstep4の説明を修正しました。また練習として半開区間を用いたstep5.cppを追加しました🙇♂️ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
閉区間で考えているため、 middle == end の場合もあります。そのため、 middle < end とはかけないと思います。 start < end、start <= middle、middle <= end と書くしかないように思います。 |
||
| return start; | ||
| } | ||
| int middle = (start + end) / 2; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 今回の問題の制約では起こりえないのですが、値が大きい場合のオーバーフローを避けるため、
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. leetcodeにも開設ございました。 |
||
| if (nums[middle] == target) { | ||
| return middle; | ||
| } | ||
|
|
||
| if (nums[middle] < target) { | ||
| return SearchInsertIndex(middle + 1, end, nums, target); | ||
| } | ||
|
|
||
| return SearchInsertIndex(start, middle - 1, nums, target); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| class Solution { | ||
| public: | ||
| int searchInsert(vector<int>& nums, int target) { | ||
| return SearchInsertIndex(0, nums.size() - 1, nums, target); | ||
| } | ||
|
|
||
| private: | ||
| int SearchInsertIndex(int start, int end, vector<int>& nums, int target) { | ||
| if (start > end) { | ||
| return start; | ||
| } | ||
| int middle = (start + end) / 2; | ||
| if (nums[middle] == target) { | ||
| return middle; | ||
| } | ||
|
|
||
| if (nums[middle] < target) { | ||
| return SearchInsertIndex(middle + 1, end, nums, target); | ||
| } else { | ||
| return SearchInsertIndex(start, middle - 1, nums, target); | ||
| } | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| class Solution { | ||
| public: | ||
| int searchInsert(vector<int>& nums, int target) { | ||
| return SearchInsertIndex(0, nums.size() - 1, nums, target); | ||
| } | ||
|
|
||
| private: | ||
| int SearchInsertIndex(int start, int end, vector<int>& nums, int target) { | ||
| if (start > end) { | ||
| return start; | ||
| } | ||
| int middle = (start + end) / 2; | ||
| if (nums[middle] == target) { | ||
| return middle; | ||
| } | ||
|
|
||
| if (nums[middle] < target) { | ||
| return SearchInsertIndex(middle + 1, end, nums, target); | ||
| } else { | ||
| return SearchInsertIndex(start, middle - 1, nums, target); | ||
| } | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| /* | ||
| 1.問題のモデル化 | ||
| numsの各要素をターゲット未満かターゲット以上であるかの2種類に分類する | ||
| ターゲット未満であればfalse、ターゲット以上であればtrueと見なす。 | ||
|
|
||
| 例えばnums = [1,3,5,6], target = 5 を用いると | ||
| nums = [false, false, true, true]と表すことができる | ||
| この中で一番左の true の位置を探す。 | ||
|
|
||
| 2.探索空間の定義 | ||
| 今回は閉区間として探索を行う。 | ||
|
|
||
| 3.初期値の設定 | ||
| startを0、endを配列の最後の要素nums.size() - 1とする。 | ||
|
|
||
| 4.ループ不変条件の設定 | ||
| startとendの真ん中をmiddleとして、ループの不変条件は | ||
| start < end、start <= middle、middle <= end | ||
|
|
||
| 5.探索ロジックの設計 | ||
| nums[middle] < target の場合、middleおよびその左側にtargetは存在しないので | ||
| startをmiddle + 1に更新する。 | ||
|
|
||
| nums[mid] >= target の場合、 mid の位置に対象がある場合があるため、 | ||
| 区間を狭めつつ mid を区間内に含めるため、end = mid とする。 | ||
|
|
||
| 6.検証 | ||
| ・targetがnumsのいずれよりも小さい場合 | ||
| nums[middle] < targetが常にfalseとなり、startは最初の位置のままendが狭まりstartの位置が解となる | ||
|
|
||
| ・targetがnumsのいずれよりも大きい場合 | ||
| nums[middle] < targetが常にtrueとなり、startがendに近づきstart + 1 | ||
|
|
||
| ・targetが複数存在する場合 | ||
| 最小のインデックスを返却 | ||
|
|
||
| 7.実行 | ||
| leet codeにて動作確認 | ||
|
|
||
| */ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. なんとなく理解しているか不安を感じています。 「この関数の仕事を手作業でやっているとしましょう。シフト制で SearchInsertIndex の呼び出しが起きるごとに、人が交代します。 あなたは、当番で SearchInsertIndex の呼び出しがおきたという連絡を受けて、仕事につきます。 ここまで働いている人たちがきちんと仕事をしていたら、start, end, nums, target についてどのようなことがいえますか。」 という質問に答えられますか。 まず、自分が呼び出した前任者がしていた仕事は3通りの可能性がありますね。 SearchInsertIndex(0, nums.size() - 1, nums, target);
SearchInsertIndex(middle + 1, end, nums, target);
SearchInsertIndex(start, middle, nums, target);前任者たちが正しく仕事をしていたら、どういう条件のものが送られてきますか。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
紙に書いて処理を追ったり、なんとか言葉に落とし込んだりしているもののまだスッキリしておりません。 1番最初に作業をする人からは、確認作業の範囲と確認対象とターゲットの数字が引き継がれます。 2番目作業者からは効率よく探索を行うためnums内のおおよそどこにターゲットがあるのかあたり付けをします。 この時にターゲットが真ん中より大きい場合は、下記の形で呼び出します。 ターゲットが真ん中の数字以下の場合(真ん中より大きくない場合)は下記の形で呼び出します。 3番目以降の作業者も効率よく作業をするため、前任から引き継がれた作業範囲を元に これを作業範囲がなくなるまで繰り返します。
・startはtargetの位置に対して常に左側(startの位置とtargetの位置がイコールの場合もある) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます。そうですね。正しい方向に考えていると思います。
これなんですが、一番はじめのループのときだけ、これが成立していませんよね。 これが最後に if で分岐をする羽目になった理由です。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| class Solution { | ||
| public: | ||
| int searchInsert(vector<int>& nums, int target) { | ||
| return SearchInsertIndex(0, nums.size() - 1, nums, target); | ||
| } | ||
|
|
||
| private: | ||
| int SearchInsertIndex(int start, int end, vector<int>& nums, int target) { | ||
| if (start == end) { | ||
| if (nums[start] >= target) { | ||
| return start; | ||
| } else { | ||
| return start + 1; | ||
| } | ||
| } | ||
| int middle = start + (end - start) / 2; | ||
|
|
||
| if (nums[middle] < target) { | ||
| return SearchInsertIndex(middle + 1, end, nums, target); | ||
| } else { | ||
| return SearchInsertIndex(start, middle, nums, target); | ||
| } | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| /* | ||
| 1.問題のモデル化 | ||
| ターゲットより大きいのか、以下なのか2つの状態に分類することができるのでtrueとfalseの問題とみなす。 | ||
| 記問題を解くにあたり、対象の位置を含む区間をstartからendとして定義する。 | ||
|
|
||
| 2.探索空間の定義 | ||
| 探索範囲を0からnとして、半開区間とみなし探索を行う。 | ||
| ターゲットより大きい場合をtrueとして[true)となる箇所を探す。 | ||
|
|
||
| 3.初期値の設定 | ||
| startを0、endをnums.size()として探索する。 | ||
|
|
||
| 4.ループ不変条件の設定 | ||
| startとendの真ん中をmiddleとして、ループの普遍条件は | ||
| start < end、start <= middle、middle <= end | ||
|
|
||
| 5.探索ロジックの設計 | ||
| nums[middle] < targetがtrueであれば、middleより左側にtargetは存在しないので | ||
| startをmiddle + 1に更新 | ||
|
|
||
| nums[middle] >= targetがtrueであれば、middleを含めmiddle以下のどこかにtargetは存在するので | ||
| ループ不変条件を守るように[start, middle)となるように範囲を狭める。 | ||
|
|
||
| 6.検証 | ||
| ・targetがnumsのいずれよりも小さい場合 | ||
| nums[middle] < targetが常にfalseとなり、startは最初の位置のままendが狭まりstartの位置が解となる | ||
|
|
||
| ・targetがnumsのいずれよりも大きい場合 | ||
| nums[middle] < targetが常にtrueとなり、startがendに近づく、start <= target < endとなっているので | ||
| startの位置が解となる | ||
|
|
||
| ・targetが複数存在する場合 | ||
| 最小のインデックスを返却 | ||
|
|
||
| 7.実行 | ||
| leet codeにて動作確認 | ||
|
|
||
| */ | ||
| class Solution { | ||
| public: | ||
| int searchInsert(vector<int>& nums, int target) { | ||
| return SearchInsertIndex(0, nums.size(), nums, target); | ||
| } | ||
|
|
||
| private: | ||
| int SearchInsertIndex(int start, int end, vector<int>& nums, int target) { | ||
| if (start == end) { | ||
| return start; | ||
| } | ||
| int middle = start + (end - start) / 2; | ||
|
|
||
| if (nums[middle] < target) { | ||
| return SearchInsertIndex(middle + 1, end, nums, target); | ||
| } else { | ||
| // nums[middle] >= target | ||
| return SearchInsertIndex(start, middle, nums, target); | ||
| } | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| class Solution { | ||
| public: | ||
| int searchInsert(vector<int>& nums, int target) { | ||
| int start = 0; | ||
| int end = nums.size(); | ||
|
|
||
| while (start < end) { | ||
| int middle = (start + end) / 2; | ||
|
|
||
| if (nums[middle] == target) { | ||
| return middle; | ||
| } | ||
| if (nums[middle] < target) { | ||
| start++; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. こうすると、target が大きい場合、start が一つずつ増えていくので、全部舐めることになりますね。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @oda |
||
| } | ||
| if (nums[middle] > target) { | ||
| end = middle; | ||
| } | ||
| } | ||
|
|
||
| return end; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| class Solution { | ||
| public: | ||
| int searchInsert(vector<int>& nums, int target) { | ||
| int start = 0; | ||
| int end = nums.size(); | ||
|
|
||
| while (start < end) { | ||
| int middle = (start + end) / 2; | ||
|
|
||
| if (nums[middle] == target) { | ||
| return middle; | ||
| } | ||
| if (nums[middle] < target) { | ||
| start = middle + 1; | ||
| } | ||
| if (nums[middle] > target) { | ||
| end = middle; | ||
| } | ||
| } | ||
|
|
||
| return end; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
二分探索についてはDiscord内で沢山議論がされているので、探してみると良さそうです。
自分が役に立ったなと思うのはこの辺
Yoshiki-Iwasa/Arai60#35 (comment)
Yoshiki-Iwasa/Arai60#35 (comment)
https://discord.com/channels/1084280443945353267/1084283898617417748/1282392271643345007
(上記のリンク先でOdaさんやnodchipさんが話してる内容)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seal-azarashi/leetcode#39 (comment)
こういう風にコードを変えたときにどこは動いてどこは動かないかを分かっていれば、正しく頭の中でモデルが作れていると思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Yoshiki-Iwasa @oda
資料ありがとうございます。同ジャンルの問題を解いてみてこの辺よくわかっていないと気づいたのでじっくり落とし込んでみます。