合并区间是一道非常典型的排序 + 贪心问题,几乎是面试必考题。
它不难写,但如果不理解思路,很容易写得一团乱。
本文将围绕一个核心问题展开:
为什么只要排序 + 一次遍历,就一定能合并所有区间?
一、题目要求
给定一个区间数组 intervals,其中每个区间表示为 [start, end],合并所有重叠的区间,并返回一个不重叠的区间数组。
示例:
输入:[[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
规则很简单:
- 区间有重叠就合并
- 最终结果中的区间互不重叠
二、最关键的一步:为什么一定要先排序?
这是这道题的灵魂步骤。
我们按区间的 起点 start 升序排序:
Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
排序之后,有一个非常重要的性质:
后面的区间,起点一定不小于前面的区间起点
这意味着什么?
- 我们只需要关心“当前区间”和“下一个区间”
- 不会出现“后面突然跳出一个更早开始的区间”
如果不排序,这道题几乎没法做。
三、合并区间的贪心思想
排序之后,策略就变得非常清晰:
- 维护一个当前合并中的区间
current - 依次遍历后面的区间
next - 判断
current和next是否重叠 - 能合并就扩展,不能合并就开启新区间
核心判断条件只有一句:
currentEnd >= nextStart
四、完整代码
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals == null || intervals.length == 0) {
return new int[0][];
}
// 1. 按区间起点排序
Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
List<int[]> merged = new ArrayList<>();
// 2. 初始化当前区间
int[] current = intervals[0];
merged.add(current);
// 3. 遍历后续区间
for (int i = 1; i < intervals.length; i++) {
int[] next = intervals[i];
int currentEnd = current[1];
int nextStart = next[0];
int nextEnd = next[1];
// 4. 判断是否重叠
if (currentEnd >= nextStart) {
current[1] = Math.max(currentEnd, nextEnd);
} else {
current = next;
merged.add(current);
}
}
return merged.toArray(new int[merged.size()][]);
}
}
五、逐行思路拆解
1. 边界判断
if (intervals == null || intervals.length == 0) {
return new int[0][];
}
防止空数组或空引用导致异常,这是标准防御式写法。
2. 为什么用 current = intervals[0]?
int[] current = intervals[0];
merged.add(current);
排序后,第一个区间一定是最早开始的:
- 它不可能被前面的区间合并
- 必然作为合并结果的起点
所以直接作为当前区间。
3. 判断是否重叠的本质
if (currentEnd >= nextStart)
这句话翻译成中文就是:
当前区间的右端点,是否覆盖到了下一个区间的起点
只要满足,就一定有重叠。
4. 为什么合并时只更新 end?
current[1] = Math.max(currentEnd, nextEnd);
因为:
- start 已经是最小的(排序保证)
- 合并后唯一不确定的是右边界
这是贪心的关键:
每次都把区间扩展到能覆盖的最远位置。
5. 为什么要 current = next?
else {
current = next;
merged.add(current);
}
当区间不重叠时:
- 说明前一个区间已经彻底结束
- 下一个区间必须单独作为新区间存在
六、时间与空间复杂度分析
- 排序:O(n log n)
- 单次遍历:O(n)
- 总时间复杂度:O(n log n)
空间复杂度:
- 额外使用了一个 List 存结果
- 空间复杂度:O(n)
七、这道题的本质总结
合并区间的核心只有三点:
- 排序是前提
- 重叠判断只看 end 和 start
- 贪心地维护当前最大覆盖区间