【力扣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,33,5 也算重叠)
cpp 复制代码
ans.back()[1] = max(interval[1], ans.back()[1]);
  • 合并区间:终点取两个区间中较大的那个
  • 例如 1,32,6 合并后,终点为 max(3, 6) = 6
cpp 复制代码
} else {
    ans.emplace_back(interval);
}
  • 不重叠时,直接将当前区间加入结果集

五、关键点解释

条件 含义 示例
interval[0] <= ans.back()[1] 当前区间起点 <= 上一个区间终点,有重叠 1,32,6
interval[0] > ans.back()[1] 当前区间起点 > 上一个区间终点,无重叠 1,35,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,贪心即可 dpi = 最多保留多少区间
贪心策略 按起点排序,合并重叠 按终点排序,选择不重叠的最多区间
时间复杂度 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)(结果集最坏情况)
边界情况 空数组、单区间、全部重叠、全部不重叠

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


相关推荐
拳里剑气11 小时前
C++算法:链表
c++·算法·链表
凌波粒11 小时前
LeetCode--90.子集II(回溯算法)
数据结构·算法·leetcode
旖-旎11 小时前
《LeetCode 417 太平洋大西洋水流问题 FloodFill DFS 解法》
c++·算法·深度优先·力扣·floodfill
凌波粒11 小时前
LeetCode--46.全排列(回溯算法)
数据结构·算法·leetcode
2zcode11 小时前
项目文档:基于MATLAB语音信号变声算法设计与实现
算法·matlab·语音识别
指令集梦境11 小时前
图解:单调栈算法模板(Java语言)
java·开发语言·算法
生成论实验室12 小时前
自动驾驶:一个自主运动的系统
人工智能·算法·机器学习·语言模型·机器人·自动驾驶·安全架构
sheeta199812 小时前
LeetCode 每日一题笔记 日期:2026.06.16 题目:3612. 字符串特殊符号处理
笔记·算法·leetcode
CoderYanger12 小时前
A.每日一题:2095. 删除链表的中间节点
java·数据结构·程序人生·leetcode·链表·面试·职场和发展
青山木12 小时前
Hot 100 --- 矩阵置零
线性代数·算法·leetcode·矩阵·哈希算法