📌 题目链接:leetcode.cn/problems/merge-intervals/
🔍 难度:中等 | 🏷️ 标签:数组、排序、区间合并、贪心算法
⏱️ 目标时间复杂度:O(n log n)
💾 空间复杂度:O(log n)(额外空间)
✅ 本题是 区间类问题 的经典代表,常用于考察对“重叠判断”、“区间覆盖”和“排序后贪心处理”的理解。
💡 在面试中,这类题目经常出现在 系统设计中的资源调度、日程安排、任务分配 场景中,例如:会议室预订冲突检测、CPU 时间片合并等。
🔍 题目分析
给定一个二维整数数组 intervals,每个元素 [start, end] 表示一个闭区间。要求将所有有重叠的区间进行合并,最终返回一个不重叠且覆盖全部原始区间的最小集合。
🧩 关键点解析:
- 区间是否重叠?
👉 若两个区间[a,b]和[c,d]满足b >= c,则它们有交集(包括端点相等的情况)。 - 合并规则:
👉 取左端点较小者,右端点较大者,即[min(a,c), max(b,d)]。 - 注意边界情况:
- 输入为空
- 只有一个区间
- 所有区间都不重叠
- 所有区间都重叠(如
[[1,2],[2,3],[3,4]])
📊 示例回顾:
输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释:[1,3] 和 [2,6] 重叠 → 合并为 [1,6];其余无重叠。
🧠 核心算法及代码讲解
🔥 核心思想:排序 + 贪心遍历
✅ 策略步骤:
- 按左端点升序排序:确保能合并的区间在排序后是连续的。
- 逐个扫描区间:
- 如果当前区间与上一个合并后的区间无重叠(即当前左端点 > 上一右端点),直接加入结果。
- 否则,更新上一区间的右端点为两者最大值(实现“合并”)。
🔄 为什么排序有效?
📌 关键洞察:如果区间按左端点排序,则任意两个可合并的区间必然是相邻或接近的。否则会违反排序顺序,导致矛盾(见官方证明)。
❗ 这种“排序+贪心”的模式广泛应用于:
- 区间调度问题(如会议安排)
- 最少射箭次数(LeetCode 435)
- 无重叠区间(LeetCode 435)
🧮 数学表达:
设当前区间为 [L, R],已合并最后一个区间为 [prev_L, prev_R]:
- 若
R < prev_R:无需合并(已在范围内) - 若
L <= prev_R:重叠 → 更新prev_R = max(prev_R, R) - 若
L > prev_R:不重叠 → 新建区间[L, R]
💡 小技巧:用
!merged.size()判断是否为空,避免越界访问。
🧩 解题思路(分步详解)
-
边界处理:
- 如果输入为空,直接返回空列表。
-
排序:
- 使用
sort()对整个intervals按第一个元素(左端点)升序排列。
- 使用
-
初始化结果容器:
- 创建
vector<vector<int>> merged存储答案。
- 创建
-
遍历每个区间:
- 提取当前区间
[L, R] - 判断是否与
merged.back()重叠:- 不重叠:
merged.back()[1] < L→ 推入新区间 - 重叠:更新
merged.back()[1] = max(merged.back()[1], R)
- 不重叠:
- 提取当前区间
-
返回结果
🧠 举个例子:
原始:
[[1,3],[2,6],[8,10],[15,18]]排序后仍相同(已有序)
步骤:
- 加入
[1,3][2,6]: 2 ≤ 3 → 合并 →[1,6][8,10]: 8 > 6 → 新增 →[1,6],[8,10][15,18]: 15 > 10 → 新增 →[1,6],[8,10],[15,18]
📈 算法分析
| 项目 | 分析 |
|---|---|
| 时间复杂度 | O(n log n) ✅ - 排序占主导:O(n log n) - 遍历:O(n) |
| 空间复杂度 | O(log n) ✅ - 排序递归栈空间(快速排序平均情况) - 结果数组不算额外空间(题目要求输出) |
| 稳定性 | 稳定(只要排序稳定即可) |
| 适用场景 | 所有区间合并类问题 |
🚫 常见误区:
- 忽略排序!直接暴力枚举会导致 O(n²) 时间复杂度。
- 错误判断重叠条件(如写成
L > prev_R而不是L <= prev_R)。- 忘记处理空输入。
🧱 代码(严格保留原结构 + 行注释)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 主函数:合并区间
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
// 边界处理:空输入
if (intervals.size() == 0) {
return {};
}
// 🔥 第一步:按左端点排序(升序)
sort(intervals.begin(), intervals.end());
// 🛠️ 初始化结果数组
vector<vector<int>> merged;
// 🧩 遍历每一个区间
for (int i = 0; i < intervals.size(); ++i) {
int L = intervals[i][0]; // 当前区间的左端点
int R = intervals[i][1]; // 当前区间的右端点
// 🤔 判断是否与最后一个已合并区间重叠
// 如果 merged 为空 或 当前左端点 > 最后一个右端点 → 不重叠
if (!merged.size() || merged.back()[1] < L) {
merged.push_back({L, R}); // 添加新区间
}
else {
// ✅ 重叠:更新最后一个区间的右端点
merged.back()[1] = max(merged.back()[1], R);
}
}
return merged;
}
};
// 测试主函数
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 🧪 测试用例 1
vector<vector<int>> intervals1 = {{1,3},{2,6},{8,10},{15,18}};
Solution sol;
auto result1 = sol.merge(intervals1);
cout << "Test 1: ";
for (auto& v : result1) {
cout << "[" << v[0] << "," << v[1] << "] ";
}
cout << endl;
// 🧪 测试用例 2
vector<vector<int>> intervals2 = {{1,4},{4,5}};
auto result2 = sol.merge(intervals2);
cout << "Test 2: ";
for (auto& v : result2) {
cout << "[" << v[0] << "," << v[1] << "] ";
}
cout << endl;
// 🧪 测试用例 3
vector<vector<int>> intervals3 = {{4,7},{1,4}};
auto result3 = sol.merge(intervals3);
cout << "Test 3: ";
for (auto& v : result3) {
cout << "[" << v[0] << "," << v[1] << "] ";
}
cout << endl;
return 0;
}
📝 输出示例:
Test 1: [1,6] [8,10] [15,18] Test 2: [1,5] Test 3: [1,7]
💡 面试拓展 & 进阶思考
🎯 1. 如何优化空间?
- 原地修改:若允许破坏输入,可以使用双指针从后往前压缩,但需注意顺序。
- 减少拷贝:使用
move()或引用传递避免深拷贝。
🎯 2. 多维区间如何处理?
- 三维区间?→ 按某一维度排序,再逐层合并。
- 区间树 / 线段树:适用于频繁查询和插入的动态场景。
🎯 3. 类似题目推荐:
| 题号 | 题名 | 核心技巧 |
|---|---|---|
| 435 | 无重叠区间 | 贪心选右端点小的 |
| 452 | 用最少数量的箭引爆气球 | 区间重叠判定 |
| 561 | 数组拆分 | 分组贪心 |
| 757 | 设置交替位 | 位运算 + 贪心 |
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📣 下一期预告:LeetCode 热题 100 第189题 —— 轮转数组(中等)
🔹 题目:给定一个数组,将其向右轮转
k步,其中k是非负整数。例如:[1,2,3,4,5,6,7],k=3→[5,6,7,1,2,3,4]🔹 核心思路:使用 三次反转法(Reverse Three Times),时间复杂度 O(n),空间复杂度 O(1)
🔹 考点:数组操作、原地修改、数学规律、旋转对称性
🔹 难度:中等,但非常经典,是许多数组旋转问题的基础!
💡 提示:不要用额外数组存储!掌握 reverse 函数的应用!
🔄 公式:
reverse(arr, 0, n-k)
reverse(arr, n-k, n)
reverse(arr, 0, n)
(整体反转三次)
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!
✅ 本系列将持续更新至 LeetCode Hot100 全部题目,每一篇都是你面试路上的“知识锚点”。
🚀 一起坚持,直到 Offer 到手!🎯