【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) 仍然是主导项。

参考灵神

相关推荐
SimonKing3 小时前
OpenCode AI辅助编程,不一样的编程思路,不写一行代码
java·后端·程序员
FastBean3 小时前
Jackson View Extension Spring Boot Starter
java·后端
Seven975 小时前
剑指offer-79、最⻓不含重复字符的⼦字符串
java
皮皮林55114 小时前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java
冰_河14 小时前
QPS从300到3100:我靠一行代码让接口性能暴涨10倍,系统性能原地起飞!!
java·后端·性能优化
地平线开发者15 小时前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶
董董灿是个攻城狮15 小时前
大模型连载2:初步认识 tokenizer 的过程
算法
地平线开发者16 小时前
地平线 VP 接口工程实践(一):hbVPRoiResize 接口功能、使用约束与典型问题总结
算法·自动驾驶
罗西的思考16 小时前
AI Agent框架探秘:拆解 OpenHands(10)--- Runtime
人工智能·算法·机器学习