Leecode热题100:合并区间(数组)

目录

回归本质,拆解"区间"

[1. 什么是区间?](#1. 什么是区间?)

[2. 什么是"重叠"?](#2. 什么是“重叠”?)

[3. 最核心的痛点是什么?](#3. 最核心的痛点是什么?)

第一性原理推导逻辑

[编写 C++ 代码](#编写 C++ 代码)

[1. 排序:建立数轴秩序](#1. 排序:建立数轴秩序)

[2. 线性扫描:局部决策](#2. 线性扫描:局部决策)

深度复盘

为什么排序是第一步?

为什么只比较右边界?

面试回答

第一步:明确物理模型(寻找矛盾点)

第二步:推导合并逻辑(分类讨论)

第三步:总结效率与实现


以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]

输出:[[1,6],[8,10],[15,18]]

解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]

输出:[[1,5]]

解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

示例 3:

输入:intervals = [[4,7],[1,4]]

输出:[[1,7]]

解释:区间 [1,4] 和 [4,7] 可被视为重叠区间。

(来源:Leecode)


回归本质,拆解"区间"

1. 什么是区间?

在数轴上,一个区间 [start, end] 代表一段连续的范围。

2. 什么是"重叠"?

假设有两个区间 A[s1, e1] 和 B[s2, e2]。它们重叠的本质真理是:

其中一个区间的起点,落在另一个区间的范围之内。

3. 最核心的痛点是什么?

如果数组是乱序的(如示例 3:[[4,7], [1,4]]),我们很难判断当前区间该和谁合并。为了判断 [4,7] 是否能合并,我们需要扫描整个数组去找谁的结尾大于等于 4。

  • 结论无序是效率的敌人。在数轴上处理问题,天然的物理顺序是"从左到右"。

第一性原理推导逻辑

如果我们把所有区间按起点排好序,会发生什么?

一旦按起点排序,对于连续的两个区间 A 和 B,我们能确定 s1 <= s2。此时,重叠的情况被极度简化了:

  • 情况 A:s2 <= e1。说明 B 的起点在 A 的范围内。新的右边界是这两个区间右边界的最大值,即 max(e1, e2)。

  • 情况 B:s2 > e1。说明 B 已经完全超出了 A 的范围。A 已经"定型"了,再往后的区间起点只会比 s2 更大,绝对不可能再和 A 重叠。我们可以把 A 存入结果。


编写 C++ 代码

1. 排序:建立数轴秩序

我们需要让区间按 start 升序排列。

cpp 复制代码
#include <vector>
#include <algorithm>

using namespace std;

vector<vector<int>> merge(vector<vector<int>>& intervals) {
    if (intervals.empty()) return {};

    // 第一步:基于第一性原理的"秩序化"
    // 按区间的起点进行升序排序
    sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) {
        return a[0] < b[0];
    });

    vector<vector<int>> merged;

2. 线性扫描:局部决策

我们维护一个"当前正在合并"的区间,拿它和下一个区间比较。

cpp 复制代码
// 先把第一个区间放进去作为"种子"
    merged.push_back(intervals[0]);

    for (int i = 1; i < intervals.size(); ++i) {
        // 取出结果集中最后一个已经合并好的区间(当前的基准)
        int& curr_start = merged.back()[0];
        int& curr_end = merged.back()[1];
        
        // 下一个区间的起点和终点
        int next_start = intervals[i][0];
        int next_end = intervals[i][1];

        // 核心逻辑判断:
        if (next_start <= curr_end) {
            // 情况 A:重叠了,更新当前基准的右边界
            // 为什么取 max?因为可能出现 [[1,10], [2,5]] 这种包含关系
            curr_end = max(curr_end, next_end);
        } else {
            // 情况 B:没重叠,当前的基准彻底完成,开启一个新区间
            merged.push_back(intervals[i]);
        }
    }

    return merged;
}

深度复盘

为什么排序是第一步?

如果不排序,我们需要比较 n² 次(每两个区间都比一下)。排序后,我们只需要比较相邻的区间,时间复杂度从 O(n²) 降到了 O(n log n)(排序的代价)+O(n)(扫描的代价)。

为什么只比较右边界?

因为我们已经按左边界排序了。对于后面进来的区间,它的左边界一定大于或等于当前区间的左边界。所以,右边界成了唯一的变量,决定了这段"连续能量"能否延续。


面试回答

在面试中讲解这道题时,最忌讳直接说"先排序再合并"。面试官想看的是你如何从无序中建立秩序

你可以按照以下三个层级来组织你的表达:


第一步:明确物理模型(寻找矛盾点)

"首先,合并区间的本质是处理数轴上的覆盖问题

如果区间是乱序的,比如 [[4,7], [1,4]],我无法立即知道当前区间 [4,7] 应该和谁合并,除非我扫描全集。这说明**'无序'是导致算法低效的根本原因**。

为了简化问题,我的第一直觉是建立秩序:将所有区间按照左端点(Start)进行升序排列。这样我们就把一个复杂的空间问题,转化为了一个在数轴上从左向右移动的线性问题。"


第二步:推导合并逻辑(分类讨论)

"在排好序的基础上,我们只需要观察相邻 的两个区间。假设当前合并后的区间是 A,下一个待处理的区间是 B

由于我们排过序,B 的起点一定大于等于 A 的起点。那么它们的关系只剩两种可能:

  1. 重叠(Overlapping)B 的起点 <= A 的终点。此时它们可以连成一片。新的终点取 max(A.end, B.end)。之所以取 max,是因为要处理包含关系(比如 [1, 10][2, 5])。

  2. 断开(Disjoint)B 的起点 > A 的终点。这意味着 A 已经完全封闭了,后面不可能再有区间能和 A 合并(因为后面的起点只会更靠后)。我们可以把 A 放入结果集,并把 B 当作下一个待合并的起点。"


第三步:总结效率与实现

"最后,在代码实现上,我可以使用一个结果数组(或 vector)。每次拿输入数组里的新区间与结果数组里的最后一个区间进行对比:

  • 如果重叠,直接原地修改结果数组最后一个元素的 end

  • 如果不重叠,直接 push_back 进去。

这个算法的时间复杂度主要取决于排序,是 O(nlog n);空间复杂度取决于排序所需的栈空间或结果数组,是 O(n)。"

相关推荐
VT.馒头2 小时前
【力扣】2631. 分组
javascript·算法·leetcode·typescript
永远都不秃头的程序员(互关)2 小时前
【K-Means深度探索(四)】速度与激情:MiniBatch K-Means如何驯服海量数据
算法·机器学习·kmeans
万象.2 小时前
redis数据结构hash的基本指令
数据结构·redis·哈希算法
中國龍在廣州2 小时前
35天,成了AI 模型的斩杀线
大数据·人工智能·深度学习·算法·机器人
多米Domi01110 小时前
0x3f第33天复习 (16;45-18:00)
数据结构·python·算法·leetcode·链表
罗湖老棍子11 小时前
【例4-11】最短网络(agrinet)(信息学奥赛一本通- P1350)
算法·图论·kruskal·prim
方圆工作室11 小时前
【C语言图形学】用*号绘制完美圆的三种算法详解与实现【AI】
c语言·开发语言·算法
曹仙逸11 小时前
数据结构day04
数据结构
Lips61111 小时前
2026.1.16力扣刷题
数据结构·算法·leetcode