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

参考灵神

相关推荐
小咕聊编程12 分钟前
【含文档+PPT+源码】基于spring boot的固定资产管理系统
java·spring boot·后端
roykingw13 分钟前
【终极面试集锦】如何设计微服务熔断体系?
java·微服务·面试
我命由我1234514 分钟前
Spring Cloud - Spring Cloud 微服务概述 (微服务的产生与特点、微服务的优缺点、微服务设计原则、微服务架构的核心组件)
java·运维·spring·spring cloud·微服务·架构·java-ee
それども17 分钟前
忽略Lombok构建警告
java·开发语言·jvm
用户685453759776924 分钟前
🎮 Java设计模式:从青铜到王者的代码修炼手册
java·后端
马尚道38 分钟前
Java高手速成--吃透源码+手写组件+定制开发教程
java
我命由我1234544 分钟前
Spring Cloud - Spring Cloud 注册中心与服务提供者(Spring Cloud Eureka 概述、微服务快速入门、微服务应用实例)
java·spring boot·spring·spring cloud·微服务·eureka·java-ee
liu****1 小时前
20.哈希
开发语言·数据结构·c++·算法·哈希算法
MetaverseMan1 小时前
Java Spring 框架的`@Autowired` 注解 以及依赖注入分析
java·开发语言·spring
一吃就胖的1 小时前
【给服务器安装服务器安装nacos】
java·运维·服务器