diff --git a/35.SearchInsertPosition/for.cpp b/35.SearchInsertPosition/for.cpp new file mode 100644 index 0000000..5fbb617 --- /dev/null +++ b/35.SearchInsertPosition/for.cpp @@ -0,0 +1,11 @@ +class Solution { +public: + int searchInsert(vector& nums, int target) { + for (int i = 0; i < nums.size(); i++) { + if (nums[i] >= target) { + return i; + } + } + return nums.size(); + } +}; diff --git a/35.SearchInsertPosition/memo.md b/35.SearchInsertPosition/memo.md new file mode 100644 index 0000000..4d31f1c --- /dev/null +++ b/35.SearchInsertPosition/memo.md @@ -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 diff --git a/35.SearchInsertPosition/step1.cpp b/35.SearchInsertPosition/step1.cpp new file mode 100644 index 0000000..cd930a7 --- /dev/null +++ b/35.SearchInsertPosition/step1.cpp @@ -0,0 +1,24 @@ +class Solution { +public: + int searchInsert(vector& nums, int target) { + return SearchInsertIndex(0, nums.size() - 1, nums, target); + } + +private: + int SearchInsertIndex(int start, int end, vector& 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); + } + + return SearchInsertIndex(start, middle - 1, nums, target); + } +}; diff --git a/35.SearchInsertPosition/step2.cpp b/35.SearchInsertPosition/step2.cpp new file mode 100644 index 0000000..4cf806f --- /dev/null +++ b/35.SearchInsertPosition/step2.cpp @@ -0,0 +1,23 @@ +class Solution { +public: + int searchInsert(vector& nums, int target) { + return SearchInsertIndex(0, nums.size() - 1, nums, target); + } + +private: + int SearchInsertIndex(int start, int end, vector& 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); + } + } +}; diff --git a/35.SearchInsertPosition/step3.cpp b/35.SearchInsertPosition/step3.cpp new file mode 100644 index 0000000..4cf806f --- /dev/null +++ b/35.SearchInsertPosition/step3.cpp @@ -0,0 +1,23 @@ +class Solution { +public: + int searchInsert(vector& nums, int target) { + return SearchInsertIndex(0, nums.size() - 1, nums, target); + } + +private: + int SearchInsertIndex(int start, int end, vector& 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); + } + } +}; diff --git a/35.SearchInsertPosition/step4.cpp b/35.SearchInsertPosition/step4.cpp new file mode 100644 index 0000000..00fa300 --- /dev/null +++ b/35.SearchInsertPosition/step4.cpp @@ -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にて動作確認 + +*/ +class Solution { +public: + int searchInsert(vector& nums, int target) { + return SearchInsertIndex(0, nums.size() - 1, nums, target); + } + +private: + int SearchInsertIndex(int start, int end, vector& 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); + } + } +}; diff --git a/35.SearchInsertPosition/step5.cpp b/35.SearchInsertPosition/step5.cpp new file mode 100644 index 0000000..ec818a1 --- /dev/null +++ b/35.SearchInsertPosition/step5.cpp @@ -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& nums, int target) { + return SearchInsertIndex(0, nums.size(), nums, target); + } + +private: + int SearchInsertIndex(int start, int end, vector& 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); + } + } +}; diff --git a/35.SearchInsertPosition/while.cpp b/35.SearchInsertPosition/while.cpp new file mode 100644 index 0000000..3e31e29 --- /dev/null +++ b/35.SearchInsertPosition/while.cpp @@ -0,0 +1,23 @@ +class Solution { +public: + int searchInsert(vector& 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++; + } + if (nums[middle] > target) { + end = middle; + } + } + + return end; + } +}; diff --git a/35.SearchInsertPosition/while_step2.cpp b/35.SearchInsertPosition/while_step2.cpp new file mode 100644 index 0000000..b4eac53 --- /dev/null +++ b/35.SearchInsertPosition/while_step2.cpp @@ -0,0 +1,23 @@ +class Solution { +public: + int searchInsert(vector& 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; + } +}; diff --git a/35.SearchInsertPosition/while_step3.cpp b/35.SearchInsertPosition/while_step3.cpp new file mode 100644 index 0000000..b3dc4e2 --- /dev/null +++ b/35.SearchInsertPosition/while_step3.cpp @@ -0,0 +1,61 @@ +/* +1.問題のモデル化 +numsの各要素をターゲット未満かターゲット以上であるかの2種類に分類する +ターゲット未満であればfalse、ターゲット以上であればtrueと見なす。 + +例えばnums = [1,3,5,6], target = 5 を用いると +nums = [false, false, true, true]と表すことができる +この中で一番左の true の位置を探す。 + +2.探索空間の定義 +今回は半開区間として探索を行う。 + +3.初期値の設定 +leftを0、rightを配列の最後の要素nums.size()とする。 + +4.ループ不変条件の設定 +left < right、[left, right)に解が含まれている。 + +5.探索ロジックの設計 +leftとrightの真ん中をmiddleと置く。 +nums[middle] < target の場合、middleおよびその左側にtargetは存在しないので +leftをmiddle + 1に更新する。 + +nums[mid] >= target の場合、 mid の位置に対象がある場合があるため、 +区間を狭めつつ mid を区間内に含めるため、right = mid とする。 + +6.検証 +・targetがnumsのいずれよりも小さい場合 + nums[middle] < targetが常にfalseとなり、leftは最初の位置のままrightが狭まりleftの初期位置が解となる + +・targetがnumsのいずれよりも大きい場合 + nums[middle] < targetが常にtrueとなり、leftがrightに近づきnumsの外側が挿入位置となる。 + +・targetに該当するnumが複数存在する場合 + 最小のインデックスを返却 + +7.実行 +leet codeにて動作確認 + +*/ +class Solution { + public: + int searchInsert(vector& nums, int target) { + int left = 0; + // numsの最後の要素の右側が挿入位置になる可能性がある + int right = nums.size(); + + // leftに挿入位置を寄せていく + while (left < right) { + int middle = left + (right - left) / 2; + + if (nums[middle] < target) { + left = middle + 1; + } else { + right = middle; + } + } + + return left; + } + };