-
Notifications
You must be signed in to change notification settings - Fork 0
57. Insert Interval #26
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
Open
ryosuketc
wants to merge
5
commits into
main
Choose a base branch
from
57_insert_interval
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # 57. Insert Interval | ||
|
|
||
| https://leetcode.com/problems/insert-interval/ | ||
|
|
||
| ## Comments | ||
|
|
||
| ### step1 | ||
|
|
||
| * 大雑把な方針として、[interval_start, interval_end] の区間を掲げて、overlap しなくなるまで j を進める。j が進み終わったら、その時点の [interval_start, interval_end] を push_back という方針で考え始めた (この区間を extend していく感覚)。 | ||
| * `SolutionWA1` | ||
| * しかし inner while のループ条件指定でなぜかドツボにハマって 50 分くらい考え込んでしまった | ||
| * 多分 `interval_end` (今掲げている範囲)、`newInterval` (引数で与えられた範囲)、`intervals[j]` (overlap していたので進めようとしている範囲) の 3 つを同時に処理しようとして、条件設定がよくわからなくなってしまった模様。 | ||
| * 最終的に `if (new_interval_start <= interval_end)` のように `newInterval` だけ別で処理し (一回の処理なので `if` だけでよい)、inner while では `interval_end` と `intervals[j]` に注目すればよいことに気づいて腹落ちした | ||
| * しかし `[], [5, 7]` のような入力に対して落ちてしまう。確かに overlap がない場合の処理ができないことに気づく | ||
| * ツギハギのようで嫌だなあと思いながら、`SolutionWA2` のようにフラグを立ててみる | ||
| * 最初、inner while にだけフラグを立てて、IN: `[[1,3],[6,9]], [2, 5]` -> OUT: `[[1,5],[6,9],[2,5]]` のようになってしまった | ||
| * `if (new_interval_start <= interval_end)` の中でもフラグを立てるようにした | ||
| * IN `[[1,5]], [0, 3]` -> OUT `[[1,5]]` (expect `[0, 5]`) のように前が extend されるケースが考慮されていない。 | ||
| * 1h 以上悩んだので一旦力尽きた | ||
|
|
||
| ### step2 | ||
|
|
||
| * https://github.com/huyfififi/coding-challenges/pull/26/files | ||
| * 一旦 step2 を見る。なるほど、overlap が発生する前、発生して skip する処理、残りの処理と段階を踏めばよかったのか… | ||
| * アイデアを見た段階で書き直した | ||
| * `step2.Solution`: ようやく AC した… | ||
| * これ、ループの条件で少し悩んだ。とぃうのも、1 つめのループと 2 つめのループで、メインの処理対象が異なる感覚。 | ||
| interval insertion みたいなのを考えるとき、2 本の数直線的なものを脳内でイメージしているのだが | ||
| * Loop1: `intervals[i]` が先行する数直線で、`newInterval` が後続。したがって、`intervals[i]` の end と、`newInterval` の start を比較。ここでは `insertion_start` と `insertion_end` は更新されない。 | ||
| * Loop2: `newInterval` (というか厳密には `insertion_start`, `insertion_end` で定義される区間 )が先行する数直線で、`intervals[i]` が後続。したがって、`newInterval` (というか `[insertion_start, insertion_en]`) の end と、`intervals[i]` の start を比較。 | ||
| * ちなみに step1 の extend していく感覚だと、なんとなく後ろにのみ extend される感覚があり、start の方の更新を忘れそう (step1 では実際忘れた) | ||
| * のように整理していないと、何と何を比較すればよいのかわからなくなりそう。 | ||
|
|
||
| * 余力がなかったので再帰についての部分はスキップした | ||
| * https://github.com/huyfififi/coding-challenges/pull/26/files#r2184884440 | ||
| * `newInterval` を `intervals` に放り込んでソートしてから処理する、という発想はなかった (以下一応転載) | ||
|
|
||
| ```cpp | ||
| class Solution { | ||
| public: | ||
| vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) { | ||
| intervals.push_back(newInterval); | ||
|
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. 入力を変更している点が気になりました。呼び出し側の視点に立って考えると、関数に渡した値が勝手に書き換えられているとびっくりすると思います。入力は原則変更しないか、変更する場合は関数コメントでそれを明記することをおすすめします。 |
||
| std::sort(intervals.begin(), intervals.end()); | ||
| vector<vector<int>> merged; | ||
| vector<int> last_interval = intervals.front(); | ||
| for (const auto& interval : intervals) { | ||
| if (last_interval[1] < interval[0]) { | ||
| merged.push_back(last_interval); | ||
| last_interval = interval; | ||
| } else { | ||
| last_interval[1] = max(last_interval[1], interval[1]); | ||
| } | ||
| } | ||
|
|
||
| merged.push_back(last_interval); | ||
| return merged; | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| * Python の `extend` + slice を C++ で書くとこんなかんじらしい。最後の while を書き換えられる | ||
| * とはいえ C++ だと while を使って書きたくなってしまう。十分単純なので。Python だと extend + slice という特有の syntax / function があるからいい気がするけど… | ||
|
|
||
|
|
||
| ```py | ||
| result.extend(intervals[i:]) | ||
| ``` | ||
|
|
||
| ```cpp | ||
| result.insert(result.end(), intervals.begin() + i, intervals.end()); | ||
| ``` | ||
|
|
||
| * しかし今回のドツボから学べることはなんだろう?またやりそうで怖い。 | ||
| * そもそも最初の、全部 1 ループで処理しようというのが複雑にする要因だったのだが、まあそれが必要なとき (inner loop) もあるわけで、どうやって見切りをつけるべきだったのか… | ||
| * なんか時々こういうのある気はする。ある特定の方針で書くと edge ケースの対応が妙にややこしくなるんだけど、別の procedure で考えると簡単、みたいな。ハマったときに切り替えられるとよいのだが面接でできる気がしないかも | ||
|
|
||
| ### step3 | ||
|
|
||
| * skip | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| class SolutionWA1 { | ||
| public: | ||
| vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) { | ||
| int new_interval_start = newInterval[0]; | ||
| int new_interval_end = newInterval[1]; | ||
| std::vector<std::vector<int>> result; | ||
| int i = 0; | ||
| bool has_overlap = false; | ||
| while (i < intervals.size()) { | ||
| int interval_start = intervals[i][0]; // Fixed | ||
| int interval_end = intervals[i][1]; // Temp. end. Not fixed yet. | ||
| if (new_interval_start <= interval_end) { | ||
| interval_end = std::max(interval_end, new_interval_end); | ||
| } | ||
| int j = i + 1; | ||
|
|
||
| while (j < intervals.size() && intervals[j][0] <= interval_end) { | ||
| interval_end = std::max(interval_end, intervals[j][1]); | ||
| ++j; | ||
| } | ||
| result.push_back({interval_start, interval_end}); | ||
| i = j; | ||
| } | ||
| return result; | ||
| } | ||
| }; | ||
|
|
||
|
|
||
|
|
||
| class SolutionWA2 { | ||
| public: | ||
| vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) { | ||
| int new_interval_start = newInterval[0]; | ||
| int new_interval_end = newInterval[1]; | ||
| std::vector<std::vector<int>> result; | ||
| int i = 0; | ||
| bool has_overlap = false; | ||
| while (i < intervals.size()) { | ||
| int interval_start = intervals[i][0]; // Fixed | ||
| int interval_end = intervals[i][1]; // Temp. end. Not fixed yet. | ||
| if (new_interval_start <= interval_end) { | ||
| has_overlap = true; | ||
| interval_end = std::max(interval_end, new_interval_end); | ||
| } | ||
| int j = i + 1; | ||
|
|
||
| while (j < intervals.size() && intervals[j][0] <= interval_end) { | ||
| has_overlap = true; | ||
| interval_end = std::max(interval_end, intervals[j][1]); | ||
| ++j; | ||
| } | ||
| result.push_back({interval_start, interval_end}); | ||
| i = j; | ||
| } | ||
| if (!has_overlap) { | ||
| result.push_back(newInterval); | ||
| return result; | ||
| } | ||
| return result; | ||
| } | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| class Solution { | ||
| public: | ||
| vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) { | ||
| std::vector<std::vector<int>> result; | ||
| int insertion_start = newInterval[0]; | ||
| int insertion_end = newInterval[1]; | ||
| int i = 0; | ||
| // No overlap -> push_back as is. | ||
| while (i < intervals.size() && intervals[i][1] < insertion_start) { | ||
| result.push_back(intervals[i++]); | ||
| } | ||
| // Handle overlaps | ||
| while (i < intervals.size() && intervals[i][0] <= insertion_end) { | ||
| insertion_start = std::min(insertion_start, intervals[i][0]); | ||
| insertion_end = std::max(insertion_end, intervals[i][1]); | ||
| ++i; | ||
| } | ||
| result.push_back({insertion_start, insertion_end}); | ||
|
|
||
| // The rest (if remaining) | ||
| while (i < intervals.size()) { | ||
| result.push_back(intervals[i++]); | ||
| } | ||
| return result; | ||
| } | ||
| }; |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| // Response to | ||
| // https://github.com/ryosuketc/leetcode_grind75/pull/26/files#r2543575724 | ||
|
|
||
| // 最初こんな感じで書いてみようとしてだめでした。手でやるのをイメージして書いていたんですが、それだと結局 merge が完了しているのかどうかのフラグを脳内に持っている感じがあり、それが必要なのかなという気がします。ただいずれにせよ条件判定が複雑になるような気がしました。 | ||
|
|
||
| class Solution { | ||
| public: | ||
| vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) { | ||
| std::vector<std::vector<int>> merged_intervals; | ||
| for (auto& interval : intervals) { | ||
| if (!Overlap(interval, newInterval)) { | ||
| merged_intervals.push_back(interval); | ||
| continue; | ||
| } | ||
| // where to insert newInterval? | ||
| // merge_done みたいなフラグをもつ必要がある気がする (手でやるならそうしそうな気がする)。 | ||
| // back returns a reference. | ||
| std::vector<int>& last_interval = merged_intervals.back(); | ||
| last_interval = Merge(last_interval, interval); | ||
| } | ||
| return merged_intervals; | ||
| } | ||
| private: | ||
| bool Overlap(const std::vector<int>& interval1, const std::vector<int>& interval2) { | ||
| // Get overlapped interval (not merged, but overlaped part). | ||
| int overlap_start = std::max(interval1[0], interval2[0]); | ||
| int overlap_end = std::min(interval1[1], interval2[1]); | ||
| return overlap_start <= overlap_end; | ||
| // 2 interval を始点でソートして比べたほうがわかりやすい気はする | ||
| } | ||
| std::vector<int> Merge(const std::vector<int>& interval1, const std::vector<int>& interval2) { | ||
| int merged_start = std::min(interval1[0], interval2[0]); | ||
| int merged_end = std::max(interval1[1], interval2[1]); | ||
| return {merged_start, merged_end}; | ||
| } | ||
| }; | ||
|
|
||
| // フラグがややこしいのかなと思って、1 回だけ入るループのような想定で書いてみました。こんなイメージ。 | ||
|
|
||
| // 1. newInterval が書いたカードが手持ち。interval が書いたカードを順番に取っていく (sort されている) | ||
| // 2. 手持ちの newInterval と被っていなければ順次 interval を追加していく | ||
| // 3. 被ったらまずそこで取った interval と newInterval を merge -> 被りがなくなるまで次の interval を取り続ける。この時点で newInterval を書いたカードは手持ちからなくなっているので merge は終わり。 | ||
| // 4. 被りがなくなったら残りを追加していく | ||
|
|
||
| // 116 / 158 testcases passed ではありますが、`[]` に `[5,7]` を挿入するなどのケースで落ちますね。 | ||
| // もし newInterval がどの区間とも重ならず、かつ既存の区間の間や先頭にある場合、newInterval はどこにも追加されずに処理が終わってしまうんですね。現在のロジックでは、!Overlap(重なりなし)の場合、intervals[i] を push_back するだけなので。 | ||
|
|
||
| class Solution { | ||
| public: | ||
| vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) { | ||
| std::vector<std::vector<int>> merged_intervals; | ||
| int i = 0; | ||
| while (i < intervals.size()) { | ||
| if (!Overlap(intervals[i], newInterval)) { | ||
| merged_intervals.push_back(intervals[i]); | ||
| ++i; | ||
| continue; | ||
| } | ||
| if (Overlap(intervals[i], newInterval)) { | ||
| std::vector<int> merged_interval = Merge(intervals[i], newInterval); | ||
| ++i; | ||
| while (i < intervals.size() && Overlap(intervals[i], merged_interval)) { | ||
| merged_interval = Merge(intervals[i], merged_interval); | ||
| ++i; | ||
| } | ||
| merged_intervals.push_back(merged_interval); | ||
| } | ||
| } | ||
| return merged_intervals; | ||
| } | ||
| private: | ||
| bool Overlap(const std::vector<int>& interval1, const std::vector<int>& interval2) { | ||
| // Get overlapped interval (not merged, but overlaped part). | ||
| int overlap_start = std::max(interval1[0], interval2[0]); | ||
| int overlap_end = std::min(interval1[1], interval2[1]); | ||
| return overlap_start <= overlap_end; | ||
| // 2 interval を始点でソートして比べたほうがわかりやすい気はする | ||
| } | ||
| std::vector<int> Merge(const std::vector<int>& interval1, const std::vector<int>& interval2) { | ||
| int merged_start = std::min(interval1[0], interval2[0]); | ||
| int merged_end = std::max(interval1[1], interval2[1]); | ||
| return {merged_start, merged_end}; | ||
| } | ||
| }; | ||
|
|
||
| // 最終的に Gemini に直してもらったらこうなりましたが、結局 3 パターンに分類しており、3 つループを書いたほうがわかりやすいですね。 | ||
|
|
||
| class Solution { | ||
| public: | ||
| vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) { | ||
| std::vector<std::vector<int>> merged_intervals; | ||
| int i = 0; | ||
| bool inserted = false; // newInterval を挿入したかどうかのフラグ | ||
|
|
||
| while (i < intervals.size()) { | ||
| // ケース1: intervals[i] が newInterval より完全に「右」にある | ||
| // -> 先に newInterval を入れる必要がある | ||
| if (intervals[i][0] > newInterval[1]) { | ||
| if (!inserted) { | ||
| merged_intervals.push_back(newInterval); | ||
| inserted = true; | ||
| } | ||
| merged_intervals.push_back(intervals[i]); | ||
| i++; | ||
| } | ||
| // ケース2: intervals[i] が newInterval より完全に「左」にある | ||
| // -> intervals[i] をそのまま入れる | ||
| else if (intervals[i][1] < newInterval[0]) { | ||
| merged_intervals.push_back(intervals[i]); | ||
| i++; | ||
| } | ||
| // ケース3: 重なっている (Overlap) | ||
| // -> マージして newInterval を更新し続ける(まだ push しない) | ||
| else { | ||
| newInterval = Merge(intervals[i], newInterval); | ||
| i++; | ||
| } | ||
| } | ||
|
|
||
| // ループ終了後、まだ newInterval が入っていなければ最後に追加 | ||
| if (!inserted) { | ||
| merged_intervals.push_back(newInterval); | ||
| } | ||
|
|
||
| return merged_intervals; | ||
| } | ||
|
|
||
| private: | ||
| // Merge関数はそのまま利用可能 | ||
| std::vector<int> Merge(const std::vector<int>& interval1, const std::vector<int>& interval2) { | ||
| int merged_start = std::min(interval1[0], interval2[0]); | ||
| int merged_end = std::max(interval1[1], interval2[1]); | ||
| return {merged_start, merged_end}; | ||
| } | ||
| }; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
えー、なんか素直ではない気がしています。
日本語で説明したら、「インターバルが並んでて、新しいインターバルと被ってるやつをすべてくっつける。」のですよね。
まず、「2つのインターバルを引数に取って被ってるかを判定する関数」作りませんか。で、次に「2つのインターバルをくっつける関数」です。この2つあればできませんか。
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.
step4.cpp に同じ内容をあげています。結局うまくいきませんでした…
最初こんな感じで書いてみようとしてだめでした。手でやるのをイメージして書いていたんですが、それだと結局 merge が完了しているのかどうかのフラグを脳内に持っている感じがあり、それが必要なのかなという気がします。ただいずれにせよ条件判定が複雑になるような気がしました。
フラグがややこしいのかなと思って、1 回だけ入るループのような想定で書いてみました。こんなイメージ。
116 / 158 testcases passed ではありますが、
[]に[5,7]を挿入するなどのケースで落ちますね。もし newInterval がどの区間とも重ならず、かつ既存の区間の間や先頭にある場合、newInterval はどこにも追加されずに処理が終わってしまうんですね。現在のロジックでは、!Overlap(重なりなし)の場合、intervals[i] を push_back するだけなので。
最終的に Gemini に直してもらったらこうなりましたが、結局 3 パターンに分類しており、3 つループを書いたほうがわかりやすいですね。
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.
あ、いえ、ループ3つで最終的にはいいんですが、発想として何がひっかかっているかについて考えたいというものです。
これが私は一番素直だと思いました。Gemini に書かせただけのコードだとこんなイメージです。こっちを頭に浮かべて、queue にする必要がないなど簡略化すると、Step 2 になるでしょう。
私がひっかかっていると予想しているのは、左にあるか、オーバーラップしているかどうか、という高次の概念と、[0][1]の比較という低次の操作の距離が遠いので、やっているうちに混乱するというものです。つまり、人間、頭の中に持っておけるものは多くないので、低次の操作と高次の概念の対応関係を一回手放したいということです。そのために一旦関数にして考えてはどうかというアドバイスです。あと、範囲 for 文で回そうとしたのも理由でしょうか。
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.
なるほど、意図は理解しました。
また範囲 for / for を使おうとしたのも確かに一因であるように思います。
この問題、そんなに難しくはない気がするのですが、私がやった方針で行くとかなりハマってしまって (計算量ではなく、処理やフラグ管理が複雑化する)、一方で3ループに分ける方針だと素直に実装できる印象でした。簡単そうな問題であるんだけどコードに落とそうとすると煩雑そうで手が止まる感じがありました。
こうした形のハマり方をときどきする印象があり、原因や対処などありますかね…
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.
なるほど。発想のときに「手でできるか」の後に「複数人でシフトを組んでできるか」という話をしています。
なぜ、上で考えるときに queue に移したか、というとテッシュのボックスみたいに、一枚ずつ紙を引っ張ると出てくるような仕組みのほうが、複数人でシフトを組む上で都合がいいからです。できることが制限されていますからランダムアクセスできる vector と一つずつ増やすインデックスの組より都合がいいです。でも、最終的には後者にするでしょう。
フラグの管理をするというのは、複数人でシフトを組む上では、部屋に大きなホワイトボードを置いておいて、従業員たちに参照や書き換えをさせるということです。一方行にしか状態が遷移していかないならば、別のマニュアルにしてマニュアルの取り換えをしたほうがいいでしょう。
意味として同じもので表現として違うものがあるときにどちらを取るか変形するか、みたいな高次の力が弱そうです。
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.
ここのコードの整え方みたいな話です。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.9kpbwslvv3yv
また、逆方向の抽象化もあるかもしれません。intervals1 と intervals2 という2つのティッシュボックスがあって、全部のテッシュをマージしたいです。
intervals2 には newInterval と書かれたティッシュだけ入れればいいです。
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.
また、Gemini
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.
こういうのもできますね。