【力扣100题】57.合并区间

题目描述

给你一个若干个区间的集合 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] 可被视为重叠区间。

解题思路总览

方法 核心思想 时间复杂度 空间复杂度 备注
排序 + 贪心 先按起点排序,后遍历合并 O(n log n) O(n) 推荐解法
模拟法 两两比较所有区间 O(n^2) O(n) 会超时

一、排序 + 贪心(最常用)

核心思想

先按区间的起点进行升序排序,排序后相邻的区间才有可能是需要合并的区间。然后遍历区间,维护一个结果集,如果当前区间与结果集中最后一个区间有重叠,则合并,否则加入结果集。

状态转移逻辑

复制代码
如果当前区间起点 <= 结果集最后一个区间终点:
  -> 有重叠,合并(取两区间终点的较大值)

否则:
  -> 无重叠,直接加入结果集

图解

复制代码
intervals = [[1,3],[2,6],[8,10],[15,18]]

第一步:排序(按起点升序)
  原顺序: [[1,3],[2,6],[8,10],[15,18]]
  排序后: [[1,3],[2,6],[8,10],[15,18]]  (已按起点排好,无需修改)

第二步:遍历合并

  ans = []

  处理 [1,3]:
    ans 为空,直接加入
    ans = [[1,3]]

  处理 [2,6]:
    2 <= 3(上一个终点),有重叠
    合并:ans.back()[1] = max(6, 3) = 6
    ans = [[1,6]]

  处理 [8,10]:
    8 > 6(上一个终点),无重叠
    ans = [[1,6],[8,10]]

  处理 [15,18]:
    15 > 10(上一个终点),无重叠
    ans = [[1,6],[8,10],[15,18]]

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

二、算法流程图

详细合并过程

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

初始化:
  ans = []
  排序后: [[1,3],[2,6],[8,10],[15,18]]

遍历 i=0, [1,3]:
  ans = []
  ans.empty()? 是
  ans.push_back([1,3])
  ans = [[1,3]]

遍历 i=1, [2,6]:
  ans.back() = [1,3]
  interval[0]=2, ans.back()[1]=3
  2 <= 3? 是,重叠
  ans.back()[1] = max(6, 3) = 6
  ans = [[1,6]]

遍历 i=2, [8,10]:
  ans.back() = [1,6]
  interval[0]=8, ans.back()[1]=6
  8 <= 6? 否,不重叠
  ans.push_back([8,10])
  ans = [[1,6],[8,10]]

遍历 i=3, [15,18]:
  ans.back() = [8,10]
  interval[0]=15, ans.back()[1]=10
  15 <= 10? 否,不重叠
  ans.push_back([15,18])
  ans = [[1,6],[8,10],[15,18]]

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

三、完整代码实现

cpp 复制代码
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        // 按区间起点排序
        sort(intervals.begin(), intervals.end());

        vector<vector<int>> ans;

        for (auto& interval : intervals) {
            // 如果当前区间起点 <= 上一个区间终点,说明有重叠
            if (!ans.empty() && interval[0] <= ans.back()[1]) {
                // 合并:终点取两区间中较大的值
                ans.back()[1] = max(interval[1], ans.back()[1]);
            } else {
                // 无重叠,直接加入
                ans.emplace_back(interval);
            }
        }

        return ans;
    }
};

四、逐行解析

cpp 复制代码
sort(intervals.begin(), intervals.end());
  • 按区间起点升序排列
  • 排序后,相邻区间才有可能是需要合并的区间
  • 如果不排序,需要 O(n^2) 的两两比较
cpp 复制代码
vector<vector<int>> ans;
  • 创建结果集,存储合并后的区间
cpp 复制代码
for (auto& interval : intervals) {
  • 遍历每个区间
cpp 复制代码
if (!ans.empty() && interval[0] <= ans.back()[1]) {
  • !ans.empty() 检查结果集是否为空,避免访问空数组
  • interval[0] <= ans.back()[1] 检查当前区间起点是否小于等于上一个区间的终点
  • 如果为真,说明两个区间重叠或相邻([1,3] 和 [3,5] 也算重叠)
cpp 复制代码
ans.back()[1] = max(interval[1], ans.back()[1]);
  • 合并区间:终点取两个区间中较大的那个
  • 例如 [1,3] 和 [2,6] 合并后,终点为 max(3, 6) = 6
cpp 复制代码
} else {
    ans.emplace_back(interval);
}
  • 不重叠时,直接将当前区间加入结果集

五、关键点解释

条件 含义 示例
interval[0] <= ans.back()[1] 当前区间起点 <= 上一个区间终点,有重叠 [1,3] 和 [2,6]
interval[0] > ans.back()[1] 当前区间起点 > 上一个区间终点,无重叠 [1,3] 和 [5,7]
区间相邻 [1,3], [3,5] 3 <= 3,也算重叠,需合并为 [1,5] -

边界情况分析

情况 处理方式 示例
空数组 返回空 vector intervals = []
单个区间 直接返回 intervals = [[1,3]]
区间完全不相交 全部保留 [[1,2],[5,6]] -> [[1,2],[5,6]]
区间完全包含 保留最大的那个 [[1,5],[2,3]] -> [[1,5]]
区间首尾相接 合并为一个区间 [[1,3],[3,5]] -> [[1,5]]

六、与第 435 题(无重叠区间)对比

维度 第 56 题 合并区间 第 435 题 无重叠区间
问题类型 合并重叠区间 移除最少的区间使无重叠
操作 直接合并 需要计算移除数量
dp 含义 无需 dp,贪心即可 dp[i] = 最多保留多少区间
贪心策略 按起点排序,合并重叠 按终点排序,选择不重叠的最多区间
时间复杂度 O(n log n) O(n log n)

七、三种操作的贪心选择

复制代码
合并区间时,对于每个区间只有两种选择:

1. 与上一个区间合并
   条件:interval[0] <= ans.back()[1]
   操作:ans.back()[1] = max(ans.back()[1], interval[1])

2. 作为新区间加入
   条件:interval[0] > ans.back()[1]
   操作:ans.push_back(interval)

没有"删除"操作,因为合并区间是保留所有区间,只是合并重叠部分。

复杂度分析

方法 时间复杂度 空间复杂度 备注
排序 + 贪心 O(n log n) O(n) 排序是瓶颈
模拟法 O(n^2) O(n) 会超时

详细分析:

  • 时间复杂度:排序 O(n log n) + 遍历 O(n) = O(n log n)
  • 空间复杂度:结果集最坏情况 O(n)(所有区间都不重叠)

面试追问 FAQ

问题 回答要点
Q: 为什么需要排序? 排序后,相邻的区间才是可能需要合并的区间,否则需要 O(n^2) 的两两比较来判断重叠
Q: 能否不排序? 可以,但时间复杂度会变成 O(n^2),面试中不推荐
Q: 如果要求原地修改怎么办? 在原数组上直接修改,排序后遍历合并,结果写回原数组
Q: 如何处理多维区间(如矩形合并)? 按 x 坐标排序后遍历,再按 y 坐标检查是否能合并,较复杂
Q: 如何处理区间有负数的情况? 排序只关心相对大小,负数不影响,直接排序即可
Q: 区间数量很大时如何优化? 可以用扫描线算法,但本题贪心解法已经是 O(n log n) 最优

相关题目

题目编号 题目名称 难度 核心差异
56 合并区间 中等 基础题,合并重叠区间
435 无重叠区间 中等 删除最少的区间使无重叠
452 用最少数量的箭引爆气球 中等 排序后贪心,区间包含判断
986 区间列表的交集 中等 双指针求交集
1288 删除被覆盖的区间 中等 删除被覆盖的区间

总结

要点 内容
核心思想 先排序,后贪心合并
排序依据 按区间起点升序
关键判断 interval[0] <= ans.back()[1] 判断是否有重叠
合并方式 起点取较小值,终点取两区间中较大值
时间复杂度 O(n log n)(排序是瓶颈)
空间复杂度 O(n)(结果集最坏情况)
边界情况 空数组、单区间、全部重叠、全部不重叠

合并区间是经典的区间贪心问题,核心在于排序后只需要一次遍历即可完成合并,思路清晰,代码简洁。


相关推荐
圣保罗的大教堂1 小时前
leetcode 33. 搜索旋转排序数组 中等
leetcode
玉树临风ives1 小时前
atcoder ABC 458 题解
数据结构·c++·算法
AKA__Zas1 小时前
芝士算法 (双指针篇2.0)
java·数据结构·leetcode·学习方法
如竟没有火炬1 小时前
有序矩阵中第K小的元素
数据结构·线性代数·算法·leetcode·矩阵·深度优先
叁散1 小时前
ESP32智能闹钟系统实验报告
单片机·嵌入式硬件·算法
Realdagongzai1 小时前
Linux 6.19.10 内核调度器算法详解
linux·学习·算法·spring·kernel
洛水水2 小时前
【力扣100题】63.最小覆盖子串
算法·leetcode
AllData公司负责人2 小时前
亲测丝滑,体验跃迁|AllData通过集成开源项目RustFS,多模态数据存储新范式
java·大数据·数据库·算法·数据分析·rustfs
磊 子2 小时前
AVL树的讲解
数据结构·算法