👋 欢迎阅读

🎯 欢迎来到「合并区间」题解之旅! 本文将带你从“将重叠区间合并成不重叠区间”这一数组处理问题出发,深入理解排序 + 一次遍历的经典解法,并掌握如何通过按左端点排序、动态维护右端点高效完成合并。
在开始之前,建议你先:
-
了解题目背景:这是 LeetCode 56 题,给定若干区间
[start, end],要求合并所有重叠的区间,返回一个恰好覆盖所有输入区间的不重叠区间数组。本质上,我们需要先排序让相邻区间有序,再通过贪心合并——若当前区间与上一个合并后的区间有重叠,则更新右端点为二者最大值;否则直接加入结果集。 -
明确学习目标:掌握排序 + 一次遍历的核心流程——按区间左端点升序排序,维护一个结果列表,依次遍历区间并判断是否与最后一个区间重叠(
当前start <= 上一end),重叠则合并(更新end = max(end, 当前end)),不重叠则加入。理解这种贪心合并策略的正确性(排序后只需考虑相邻区间,局部合并即可得到全局结果),并熟练处理边界情况(如空数组、只有一个区间、完全包含的区间等)。 -
准备好环境:建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如
intervals = [[1,3],[2,6],[8,10],[15,18]]输出[[1,6],[8,10],[15,18]])。
本文将从问题转化、排序合并策略设计、代码实现到复杂度分析,层层递进。即使你对区间处理还不熟悉,我们也会从“按起点排好队,一个个看能不能接上”这一直觉出发,让你轻松抓住核心思想——排序之后,所有需要合并的区间一定是相邻的,所以只需一趟扫描就能完成合并。现在,让我们一起把重叠的区间“粘合”起来,合并成最简洁的结果吧! 📏🔗
一、题目

二、做题思路
1. 问题分析(前置分析)
给定一组区间 [start, end],要求 合并所有重叠的区间,返回一个 不重叠 的区间数组,使其 恰好覆盖输入中的所有区间。
核心贪心思想:先按照区间 左端点升序排序,这样所有可能重叠的区间都会相邻。然后 顺序扫描,维护一个当前合并区间,若下一个区间的左端点 ≤ 当前合并区间的右端点,则 合并(更新右端点为两者最大值);否则,断开,将当前合并区间存入结果,并开始新区间。
2. 贪心策略(核心决策规则)
-
第一步:将
intervals按 左端点升序排序(若左端点相同,按右端点升序,但本题左端点排序已足够)。 -
第二步:初始化 当前合并区间 为第一个区间,记录其
left和right。 -
第三步:从第二个区间开始遍历:
-
若当前区间的 左端点 ≤ 当前合并区间的右端点,说明有重叠,合并:
right = max(right, currentRight)。 -
否则,说明无重叠,将当前合并区间存入结果,并 更新当前合并区间为当前遍历到的区间。
-
-
第四步:遍历结束后,将最后一个合并区间加入结果。
3. 正确性说明(简单版本)
按左端点排序后,所有可能重叠的区间一定连续排列在一起。因此,顺序扫描时,若当前区间的左端点 ≤ 当前合并区间的右端点,则它们必然重叠,必须合并。若左端点 > 右端点,则由于后续区间的左端点 ≥ 当前区间的左端点,它们也不可能与当前合并区间重叠,因此可以安全断开,开始新区间。这种 “能合并就合并” 的贪心策略不会遗漏任何重叠情况,最终得到的是互不重叠且覆盖完整的区间集合,是全局最优解。
4. 实现细节(边界防护)
-
先对
intervals排序,时间复杂度 O(n log n)。 -
使用
left和right维护当前合并区间的左右端点,初始为第一个区间的端点。 -
遍历时,比较
a <= right判断是否重叠,其中a是当前区间的左端点。 -
若不重叠,将
{left, right}加入结果,并 更新left = a, right = b。 -
遍历结束后,务必加入最后一个合并区间(循环内未处理最后一段)。
-
若输入为空,需要特殊处理(但题目保证
intervals.length >= 1)。
5. 返回值(目标映射)
返回 ret,即 合并后的不重叠区间数组。
四、代码
class Solution
{
public:
vector<vector<int>> merge(vector<vector<int>>& intervals)
{
// 1. 先按区间左端点升序排序,保证后续合并只需顺序扫描
sort(intervals.begin(), intervals.end());
int row = intervals.size();
int col = intervals[0].size();
vector<vector<int>> ret; // 存储合并后的区间
// 初始化当前合并区间的左右端点(第一个区间的左右端点)
int left = intervals[0][0];
int right = intervals[0][1];
// 2. 从第二个区间开始遍历(因为第一个已作为初始合并区间)
for (int i = 1; i < row; i++)
{
int a = intervals[i][0]; // 当前区间的左端点
int b = intervals[i][1]; // 当前区间的右端点
// 如果当前区间的左端点 <= 当前合并区间的右端点,说明有重叠,需要合并
if (a <= right)
{
// 合并后右端点取两者最大值(左端点因为排序已经确保 a >= left,所以左端点不变)
right = max(right, b);
}
else
{
// 否则没有重叠,将当前合并区间加入结果集,并开始新的合并区间
vector<int> value;
value.push_back(left);
value.push_back(right);
ret.push_back(value);
// 新合并区间初始化为当前区间
left = a;
right = b;
}
}
// 3. 将最后一个合并区间加入结果集(循环中最后一段未加入)
vector<int> value;
value.push_back(left);
value.push_back(right);
ret.push_back(value);
// 4. 返回合并后的区间列表
return ret;
}
};
五、流程图
六、正确性说明(详细版)
步骤 1:符号与问题建模
+----------------------------------------------------+
| 输入:区间列表 intervals,每个区间 [start, end] |
| 目标:合并所有重叠区间,返回不重叠的区间列表 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 贪心策略:先按左端点升序排序,然后顺序扫描。 |
| 维护当前合并区间 [left, right]。 |
| 遍历下一区间 [a, b]: |
| - 若 a <= right(重叠),则合并 right = max(right,b)|
| - 否则(不重叠),将 [left,right] 加入结果, |
| 新合并区间设为 [a, b]。 |
| 贪心实质:**排序后,若当前区间与合并区间重叠**, |
| 则合并它一定是最优的(因为所有后续区间左端点更大,|
| 不会影响已作出的合并决策)。 |
+----------------------------------------------------+
-
按左端点升序排序,保证后续区间左端点 ≥ 当前区间左端点。
-
维护一个当前合并区间
[left, right],初始为第一个区间。 -
贪心选择:每次遇到新区间时,若其左端点 ≤ 当前右端点,说明重叠,必须合并;否则无法再与当前区间重叠,当前区间可封存。
步骤 2:关键性质 —— 贪心选择的正确性(反证法)
+----------------------------------------------------+
| 性质 1:排序后,若当前区间 [a,b] 与合并区间 |
| [left,right] 满足 a <= right,则二者必然重叠, |
| 且合并后的区间为 [left, max(right,b)]。 |
| 反证:若不合并,则最终结果中这两个区间将分别存在,|
| 但它们的并集是连续的,合并不会影响其他区间, |
| 因此不合并只会使区间数增多,不可能更优。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 性质 2:若 a > right,则当前区间与合并区间以及 |
| 之前所有已处理区间均不重叠。 |
| 证明:之前所有区间的右端点 ≤ right(因为合并区间 |
| 右端点是已处理区间右端点的最大值),而 a > right,|
| 所以 a 大于之前所有区间的右端点,**不可能重叠**。 |
| 因此可以安全地将当前合并区间加入结果。 |
+----------------------------------------------------+
详细论证:
-
性质1(重叠必须合并):当
a <= right时,当前区间[a,b]与合并区间[left,right]有交集(因为a <= right且left <= a)。如果不合并,则两个区间在最终结果中会分开存在,但它们实际上覆盖了一个连续范围,合并后不会影响任何其他区间(因为其他区间要么左端点更大,要么已被处理)。因此合并是安全的,且能使区间数更少,必然更优。 -
性质2(不重叠则安全封存):当
a > right时,由于所有已处理区间的右端点都不超过right(right是已处理区间的右端点最大值),而a大于right,所以当前区间的左端点大于所有已处理区间的右端点。这意味着当前区间与之前所有区间都无重叠。因此可以放心地将[left,right]加入结果,并开始新的合并区间。
步骤 3:归纳证明 —— 扫描过程得到全局最优
+----------------------------------------------------+
| 初始:排序后,合并区间为第一个区间。 |
| 归纳假设:处理完前 i 个区间后,已加入结果的区间 |
| 互不重叠且覆盖了前 i 个区间的并集,当前合并区间 |
| 是剩余未封存区间的并集。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 处理第 i+1 个区间 [a,b]: |
| - 若 a <= right,由性质1,合并,right 更新。 |
| - 否则,由性质2,将当前合并区间封存,开始新区间。 |
| 归纳假设保持。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 遍历结束后,将最后的合并区间加入结果。 |
| 最终结果覆盖所有区间,且互不重叠,即为最优解。 |
+----------------------------------------------------+
详细论证:
-
归纳基础:处理前,结果为空,合并区间为第一个区间,显然覆盖了已处理的1个区间。
-
归纳步骤:假设已处理前
i个区间,结果中的区间互不重叠且覆盖了前i个区间的并集,当前合并区间[left,right]是前i个区间中尚未封存部分的并集(即最右侧可能继续扩展的区间)。考虑第i+1个区间[a,b](排序后a >= left):-
若
a <= right,说明新区间与当前合并区间重叠,合并后右端点变为max(right,b),新合并区间覆盖了前i+1个区间的并集,且结果中的区间仍互不重叠(因为封存部分未变)。 -
若
a > right,由性质2,新区间与当前合并区间及之前所有区间均不重叠。因此将当前合并区间封存到结果中,新的合并区间设为[a,b]。结果中的区间仍互不重叠,且覆盖了前i+1个区间的并集。
-
-
终止:遍历所有区间后,将最后一个合并区间加入结果。最终结果满足互不重叠且覆盖所有区间,贪心解即为最优解(因为每次合并决策都是强制或最优的,没有其他选择能减少区间总数)
🎯 闭幕
🎉 恭喜你完成了「合并区间」问题的学习!
为了巩固知识并进一步拓展,建议你:
🚀 动手实践
在 LeetCode 上提交代码,尝试不同的测试用例。
💡 深入思考
-
本题采用 排序 + 贪心合并 策略:先按区间左端点升序排序,然后依次判断当前区间是否与已合并区间重叠,若重叠则扩展右端点,否则保存并开启新区间。为什么按左端点排序后,只需要和“当前合并区间”比较,而无需与更早的区间比较?
-
代码中判断重叠的条件是
a <= right,这表示当前区间左端点 ≤ 已合并区间右端点。如果a == right也算重叠吗? 示例 2 中[1,4]和[4,5]合并为[1,5],说明相等情况确实需要合并,代码中的<=是正确的。
📚 延伸挑战
-
如果区间是 左闭右开
[start, end),合并逻辑需要改变吗?
如果你觉得本文对你有所帮助,欢迎:
👍 点赞 / 收藏
👤 关注作者,获取更多题解
💬 留言交流你的疑问或优化思路
祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨
 —— 题解&spm=1001.2101.3001.5002&articleId=163136230&d=1&t=3&u=c086d557d4fc4bddb17012fa02703f02)
333

被折叠的 条评论
为什么被折叠?



