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

Problem: 56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervalsi = 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) 仍然是主导项。

参考灵神

相关推荐
其实防守也摸鱼2 分钟前
信创安全竞赛知识总结与题库
大数据·网络·人工智能·算法·安全·php
SL_staff37 分钟前
打破风控迭代死循环:用决策编排重构业务与技术协作链路
java·产品·全栈
Mikko71 小时前
Redis 连接数满了服务端有错误信息吗?连接数不增加却命令超时?6.2 实测连接池、超时与大 key 怎么判定
java·spring boot·redis·后端
.Bryce.1 小时前
从零到一开发技术名词大全:从需求分析到 AI Agent 工程
java·网络·人工智能·agent·需求分析·agent评测
小超人诞生日记1 小时前
C++ 智能指针的使用及其原理详解
c++·算法
SL_staff1 小时前
JVS-APS的7个刚性约束设计:从无限产能幻觉到可执行排产的工程实践
java·spring cloud·全栈
代码山河3 小时前
基本类型转换:自动类型转换与强制类型转换
java·学习·架构·教程·面向对象·项目
Escalating_xu4 小时前
【Linux 网络编程】五种 I/O 模型与非阻塞 I/O:从“等数据”到正确处理 EAGAIN
java·linux·网络
程序猿乐锅4 小时前
万字详解Single-flight(单飞模式)
java·开发语言·分布式·后端·学习·single-flight