【LeetCode 热题 100】56. 合并区间——排序+遍历

Problem: 56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

文章目录

整体思路

这段代码旨在解决一个经典的区间问题:合并重叠区间 (Merge Intervals)。给定一个区间的集合,它需要合并所有重叠的区间,并返回一个不重叠的区间集合,该集合能覆盖输入的所有区间。

该算法的整体思路清晰且高效,主要依赖于 排序单次遍历。其核心逻辑步骤如下:

  1. 排序是关键前提

    • 算法的第一步,也是最重要的一步,是对所有区间按照它们的 起始位置 进行升序排序。
    • 目的 :排序后,我们就可以按顺序处理区间。当我们考虑一个新区间 current 时,任何可能与它重叠的区间,必然是结果集中我们刚刚处理过的最后一个区间 last 。我们无需再关心 last 之前的区间,因为它们的结束位置必然小于 last 的结束位置,而 last 的起始位置又小于等于 current 的起始位置,所以 current 不可能与 last 之前的区间重叠。
  2. 遍历与合并逻辑

    • 算法使用一个动态列表 ans 来存储最终合并好的区间。这样做的好处是,我们不需要预先知道结果集的大小。
    • 接着,遍历排序后的每一个区间 p。对于每个区间,将其与 ans 中的最后一个区间进行比较:
      • Case 1: 可合并(有重叠) 。如果 ans 不为空,并且当前区间 p 的起始位置 p[0] 小于或等于 ans 最后一个区间的结束位置 ans.get(m-1)[1],则说明它们有重叠。
        • 合并操作 :我们不添加新区间,而是更新 ans 中最后一个区间的结束位置。新的结束位置应该是原结束位置和当前区间 p 结束位置 p[1] 中的较大者。即 new_end = Math.max(last_end, p[1])。这确保了合并后的区间能完全覆盖原始的两个区间。
      • Case 2: 不可合并(无重叠) 。如果 ans 为空,或者当前区间 p 的起始位置大于 ans 最后一个区间的结束位置,则说明它们之间没有重叠,p 是一个新的、独立的区间段的开始。
        • 添加操作 :直接将当前区间 p 添加到 ans 列表的末尾。
  3. 结果转换

    • 遍历结束后,ans 列表中就包含了所有合并后的、不重叠的区间。
    • 最后,将这个 List<int[]> 转换为题目要求的 int[][] 格式并返回。

完整代码

java 复制代码
class Solution {
    /**
     * 合并所有重叠的区间。
     * @param intervals 一个包含多个区间的二维数组,每个区间为 [start, end]。
     * @return 一个不重叠的区间集合,覆盖了输入的所有区间。
     */
    public int[][] merge(int[][] intervals) {
        // ans: 用于动态存储合并后的结果区间。使用List因为最终区间数量不确定。
        List<int[]> ans = new ArrayList<>();
        
        // 核心步骤 1: 按区间的起始位置进行升序排序。
        // 这是高效合并的前提。
        // (p, q) -> p[0] - q[0] 是一个 lambda 表达式,用于定义比较器。
        Arrays.sort(intervals, (p, q) -> p[0] - q[0]);
        
        // 核心步骤 2: 遍历排序后的每一个区间
        for (int[] p : intervals) {
            // 获取当前结果集的大小
            int m = ans.size();
            
            // 判断当前区间 p 是否可以与结果集中的最后一个区间合并
            // 条件:1. 结果集不为空 (m > 0)
            //       2. 当前区间的起始 <= 最后一个区间的结束
            if (m > 0 && p[0] <= ans.get(m - 1)[1]) {
                // 合并操作:不添加新区间,而是更新最后一个区间的结束边界。
                // 新的结束边界是原结束边界和当前区间结束边界中的较大值。
                ans.get(m - 1)[1] = Math.max(ans.get(m - 1)[1], p[1]);
            } else {
                // 无重叠:将当前区间作为一个新的独立区间添加到结果集中。
                ans.add(p);
            }
        }
        
        // 核心步骤 3: 将 List<int[]> 转换为 int[][] 格式并返回。
        return ans.toArray(new int[ans.size()][2]);
    }
}

时空复杂度

时间复杂度:O(N log N)

  1. 排序Arrays.sort(intervals, ...) 是算法中时间开销最大的部分。对一个包含 N 个区间的数组进行排序,其平均和最坏情况下的时间复杂度为 O(N log N)
  2. 遍历for (int[] p : intervals) 循环遍历所有 N 个区间一次。
  3. 循环内部操作
    • ans.size()ans.get(m-1)ans.add(p) 这些对 ArrayList 的操作,在均摊意义下都是 O(1) 的。
    • 因此,整个循环体的时间复杂度为 N * O(1) = O(N)

综合分析

算法的总时间复杂度由各部分相加决定:O(N log N) (排序) + O(N) (遍历)。在 Big O 表示法中,我们只保留最高阶项,所以最终的时间复杂度是 O(N log N)

空间复杂度:O(N)

  1. 主要存储开销
    • List<int[]> ans:用于存储结果。在最坏的情况下,如果所有区间都互不重叠,那么 ans 列表将需要存储所有 N 个原始区间。因此,这部分空间是 O(N)
    • ans.toArray(...):这个操作会创建一个新的 int[][] 数组,其大小也与 ans 列表相同,为 O(N)。
  2. 排序的额外空间
    • Java 中 Arrays.sort 对于对象数组使用的是 Timsort 算法。Timsort 是一种混合排序算法,其空间复杂度在最好的情况下是 O(1),在平均情况下是 O(log N),在最坏的情况下是 O(N)。

综合分析

算法所需的额外空间主要由存储结果的 ans 列表决定。考虑到最坏情况,空间复杂度为 O(N)。即使算上排序所需的空间,O(N) 仍然是主导项。

参考灵神

相关推荐
碎叶城李白4 分钟前
若依学习笔记1-validated
java·笔记·学习·validated
Kaltistss30 分钟前
98.验证二叉搜索树
算法·leetcode·职场和发展
都叫我大帅哥30 分钟前
🌊 Redis Stream深度探险:从秒杀系统到面试通关
java·redis
都叫我大帅哥31 分钟前
Redis持久化全解析:从健忘症患者到记忆大师的逆袭
java·redis
知己如祭33 分钟前
图论基础(DFS、BFS、拓扑排序)
算法
mit6.82442 分钟前
[Cyclone] 哈希算法 | SIMD优化哈希计算 | 大数运算 (Int类)
算法·哈希算法
c++bug1 小时前
动态规划VS记忆化搜索(2)
算法·动态规划
哪 吒1 小时前
2025B卷 - 华为OD机试七日集训第5期 - 按算法分类,由易到难,循序渐进,玩转OD(Python/JS/C/C++)
python·算法·华为od·华为od机试·2025b卷
程序猿阿越1 小时前
Kafka源码(一)Controller选举与创建Topic
java·后端·源码