Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions 57_insert_interval/memo.md
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 以上悩んだので一旦力尽きた
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

えー、なんか素直ではない気がしています。
日本語で説明したら、「インターバルが並んでて、新しいインターバルと被ってるやつをすべてくっつける。」のですよね。
まず、「2つのインターバルを引数に取って被ってるかを判定する関数」作りませんか。で、次に「2つのインターバルをくっつける関数」です。この2つあればできませんか。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step4.cpp に同じ内容をあげています。結局うまくいきませんでした…


最初こんな感じで書いてみようとしてだめでした。手でやるのをイメージして書いていたんですが、それだと結局 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};
    }
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あ、いえ、ループ3つで最終的にはいいんですが、発想として何がひっかかっているかについて考えたいというものです。

  • 先頭から intervals を見ていく。左にあるやつはそのまま出力。
  • オーバーラップしていたら、newInterval を更新。(破壊するのが嫌ならば別の変数にあらかじめコピーしておく。)
  • オーバーラップしなくなったら newInterval を出力。
  • 残りをすべて出力。

これが私は一番素直だと思いました。Gemini に書かせただけのコードだとこんなイメージです。こっちを頭に浮かべて、queue にする必要がないなど簡略化すると、Step 2 になるでしょう。
私がひっかかっていると予想しているのは、左にあるか、オーバーラップしているかどうか、という高次の概念と、[0][1]の比較という低次の操作の距離が遠いので、やっているうちに混乱するというものです。つまり、人間、頭の中に持っておけるものは多くないので、低次の操作と高次の概念の対応関係を一回手放したいということです。そのために一旦関数にして考えてはどうかというアドバイスです。あと、範囲 for 文で回そうとしたのも理由でしょうか。

class Solution {
public:
    std::vector<std::vector<int>> insert(std::vector<std::vector<int>>& intervals, std::vector<int>& newInterval) {
        
        // 1. データ準備
        // intervals を queue にコピー (先頭から順に取り出すため)
        std::queue<std::vector<int>> intervals_queue;
        for (const auto& interval : intervals) {
            intervals_queue.push(interval);
        }
        
        // newInterval を作業用変数にコピー(オリジナルの newInterval を破壊しないため)
        std::vector<int> current_merged_interval = newInterval;
        
        // 出力用の結果リスト
        std::vector<std::vector<int>> result;

        // --- フェーズ 1: 左側の非オーバーラップ部分を処理 (左にあるやつはそのまま出力) ---
        // queue の先頭が current_merged_interval より完全に左にある間、結果に追加
        while (!intervals_queue.empty() && intervals_queue.front()[1] < current_merged_interval[0]) {
            result.push_back(intervals_queue.front());
            intervals_queue.pop();
        }

        // --- フェーズ 2: オーバーラップ部分を処理 (オーバーラップしていたら current_merged_interval を更新) ---
        // queue の先頭が current_merged_interval とオーバーラップしている間、マージして current_merged_interval を更新
        // オーバーラップの条件: 区間Aの始まり <= 区間Bの終わり AND 区間Bの始まり <= 区間Aの終わり
        while (!intervals_queue.empty() && Overlap(intervals_queue.front(), current_merged_interval)) {
            // マージして current_merged_interval を拡張
            current_merged_interval = Merge(intervals_queue.front(), current_merged_interval);
            intervals_queue.pop();
        }

        // --- フェーズ 3: マージ結果の挿入 (オーバーラップしなくなったら current_merged_interval を出力) ---
        result.push_back(current_merged_interval);

        // --- フェーズ 4: 右側の非オーバーラップ部分を処理 (残りをすべて出力) ---
        // queue に残っているインターバル(すべて current_merged_interval より右にある)を結果に追加
        while (!intervals_queue.empty()) {
            result.push_back(intervals_queue.front());
            intervals_queue.pop();
        }

        return result;
    }

private:
    // 2つのインターバルが重なっているかチェック
    bool Overlap(const std::vector<int>& interval1, const std::vector<int>& interval2) {
        // A.start <= B.end && B.start <= A.end
        return interval1[0] <= interval2[1] && interval2[0] <= interval1[1];
    }
    
    // 2つのインターバルをマージ
    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};
    }
};

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、意図は理解しました。
また範囲 for / for を使おうとしたのも確かに一因であるように思います。

この問題、そんなに難しくはない気がするのですが、私がやった方針で行くとかなりハマってしまって (計算量ではなく、処理やフラグ管理が複雑化する)、一方で3ループに分ける方針だと素直に実装できる印象でした。簡単そうな問題であるんだけどコードに落とそうとすると煩雑そうで手が止まる感じがありました。

こうした形のハマり方をときどきする印象があり、原因や対処などありますかね…

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど。発想のときに「手でできるか」の後に「複数人でシフトを組んでできるか」という話をしています。

なぜ、上で考えるときに queue に移したか、というとテッシュのボックスみたいに、一枚ずつ紙を引っ張ると出てくるような仕組みのほうが、複数人でシフトを組む上で都合がいいからです。できることが制限されていますからランダムアクセスできる vector と一つずつ増やすインデックスの組より都合がいいです。でも、最終的には後者にするでしょう。

フラグの管理をするというのは、複数人でシフトを組む上では、部屋に大きなホワイトボードを置いておいて、従業員たちに参照や書き換えをさせるということです。一方行にしか状態が遷移していかないならば、別のマニュアルにしてマニュアルの取り換えをしたほうがいいでしょう。

意味として同じもので表現として違うものがあるときにどちらを取るか変形するか、みたいな高次の力が弱そうです。

Copy link
Copy Markdown

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 と書かれたティッシュだけ入れればいいです。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

また、Gemini

class Solution {
public:
    // メインの汎用マージ関数
    std::vector<std::vector<int>> mergeIntervals(
        const std::vector<std::vector<int>>& intervals1,
        const std::vector<std::vector<int>>& intervals2) 
    {
        std::vector<std::vector<int>> result;
        int i = 0; // intervals1 のポインター
        int j = 0; // intervals2 のポインター

        // 1. ポインター i と j を使って、intervals1 と intervals2 をマージし、オーバーラップを処理
        while (i < intervals1.size() || j < intervals2.size()) {
            
            std::vector<int> current_interval;

            // どちらのリストから次のインターバルを取り出すか決定
            if (i < intervals1.size() && (j >= intervals2.size() || intervals1[i][0] <= intervals2[j][0])) {
                // intervals1[i] の方が先か、intervals2 がもう空の場合
                current_interval = intervals1[i++];
            } else {
                // intervals2[j] の方が先
                current_interval = intervals2[j++];
            }

            // result が空、または current_interval が result の最後の要素とオーバーラップしない場合
            if (result.empty() || current_interval[0] > result.back()[1]) {
                // 新しいインターバルとして追加
                result.push_back(current_interval);
            } else {
                // オーバーラップしている場合、result の最後の要素とマージして更新
                result.back()[1] = std::max(result.back()[1], current_interval[1]);
            }
        }

        return result;
    }

    // 元の問題 (特殊ケース) を汎用関数で実装
    std::vector<std::vector<int>> insert(
        std::vector<std::vector<int>>& intervals, 
        std::vector<int>& newInterval) 
    {
        // newInterval を vector<vector<int>> の形式に変換
        std::vector<std::vector<int>> intervals2 = {newInterval};
        
        // 汎用マージ関数を呼び出す
        return mergeIntervals(intervals, intervals2);
    }
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こういうのもできますね。

class Solution {
private:
    using IntervalPointer = std::tuple<int, int, int>; // {start, list_idx, interval_idx}

public:
    std::vector<std::vector<int>> mergeMultipleIntervalLists(
        const std::vector<std::vector<std::vector<int>>>& multiple_interval_lists) 
    {
        std::priority_queue<
            IntervalPointer, 
            std::vector<IntervalPointer>, 
            std::greater<IntervalPointer>
        > pq;

        for (int i = 0; i < multiple_interval_lists.size(); ++i) {
            if (!multiple_interval_lists[i].empty()) {
                const auto& interval = multiple_interval_lists[i][0];
                pq.push({interval[0], i, 0});
            }
        }

        std::vector<std::vector<int>> result;

        while (!pq.empty()) {
            auto [start, list_idx, interval_idx] = pq.top();
            pq.pop();
            const auto& current_interval = multiple_interval_lists[list_idx][interval_idx];
            int next_interval_idx = interval_idx + 1;
            if (next_interval_idx < multiple_interval_lists[list_idx].size()) {
                const auto& next_interval = multiple_interval_lists[list_idx][next_interval_idx];
                pq.push({next_interval[0], list_idx, next_interval_idx});
            }

            if (result.empty() || current_interval[0] > result.back()[1]) {
                result.push_back(current_interval);
            } else {
                result.back()[1] = std::max(result.back()[1], current_interval[1]);
            }
        }
        return result;
    }
};


### 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
61 changes: 61 additions & 0 deletions 57_insert_interval/step1.cpp
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;
}
};
26 changes: 26 additions & 0 deletions 57_insert_interval/step2.cpp
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 added 57_insert_interval/step3.cpp
Empty file.
135 changes: 135 additions & 0 deletions 57_insert_interval/step4.cpp
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};
}
};