LeetCode 56:合并区间(Merge Intervals)—— 一篇把思路讲清楚的笔记

68 阅读3分钟

合并区间是一道非常典型的排序 + 贪心问题,几乎是面试必考题。
它不难写,但如果不理解思路,很容易写得一团乱。

本文将围绕一个核心问题展开:

为什么只要排序 + 一次遍历,就一定能合并所有区间?


一、题目要求

给定一个区间数组 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]));

排序之后,有一个非常重要的性质:

后面的区间,起点一定不小于前面的区间起点

这意味着什么?

  • 我们只需要关心“当前区间”和“下一个区间”
  • 不会出现“后面突然跳出一个更早开始的区间”

如果不排序,这道题几乎没法做。


三、合并区间的贪心思想

排序之后,策略就变得非常清晰:

  1. 维护一个当前合并中的区间 current
  2. 依次遍历后面的区间 next
  3. 判断 currentnext 是否重叠
  4. 能合并就扩展,不能合并就开启新区间

核心判断条件只有一句:

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)

七、这道题的本质总结

合并区间的核心只有三点:

  1. 排序是前提
  2. 重叠判断只看 end 和 start
  3. 贪心地维护当前最大覆盖区间