合并区间问题

题目描述

合并区间

输入:\[1,3,2,6,8,10,15,18]

输出:\[1,6,8,10,15,18]

解释:区间 1,32,6 重叠, 将它们合并为 1,6.


算法步骤

这个问题,首先想到的是用数组列表来解决问题。算法分为以下几个步骤:

  1. 将初始所有区间按起始值排序,准备加入数组列表。
  2. 设立一个数组列表,这个数组列表内保存的是已经按起始值大小排好序的,没有交叉的区间。那么数组列表末尾是起始值最大的区间。
  3. 当新元素加入数组列表时,与末尾元素比较,如果有交叉,就修改末尾元素区间。
  4. 当新元素加入数组列表时,如果与末尾元素比较没有交叉,就直接加入数组列表中。

那么会不会出现新元素和栈内多个元素都交叉呢?不会的,因为如果与多个元素交叉,这里假设末尾元素为S0S_0S0,那么必然与倒数第二个元素S1S_1S1相交,设新元素为SSS,那么必然有以下关系式:
S0≤S11S11<S00  ⟹  S0<S00 S0 \le S_11\\ S_11 \lt S_00\\ \implies S0\lt S_00 S0≤S11S11<S00⟹S0<S00

那么新元素要比末尾元素S0S_0S0提前入栈,不符合按起始值顺序加入的假设。


Java实现

java 复制代码
package cn.edu.necpu.problem;
import java.util.*;

public class IntervalMerger {
    public static List<int[]> mergeWithStack(int[][] intervals) {
        List<int[]> result = new ArrayList<>();
        if (intervals == null || intervals.length == 0) {
            return result;
        }

        // 1. 按照区间的起始位置进行升序排序
        Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));

        // 2. 使用 ArrayList 作为容器
        ArrayList<int[]> list = new ArrayList<>();

        for (int[] current : intervals) {
            // 如果为空,或者没有交叉,就直接加入数组列表中。
            if (list.isEmpty() || list.get(list.size()-1)[1] < current[0]) {
                list.add(current);
            } else {
                // 如果有重叠,合并区间:更新末尾元素的结束位置
                int[] top = list.get(list.size()-1);
                int newEnd = Math.max(top[1], current[1]);
                // 末尾元素的结束位置
                top[1] = newEnd;
            }
        }

        return list;
    }

    // 测试代码
    public static void main(String[] args) {


        int[][] intervals = {{1,3}, {2,6}, {8,10}, {15,18}};
        List<int[]> merged = mergeWithStack(intervals);
        System.out.println("测试用例1");
        for (int[] interval : merged) {
            System.out.println("[" + interval[0] + "," + interval[1] + "]");
        }

        intervals = new int[][]{{1,3}, {2,6}, {8,10}, {1,18}};
        merged = mergeWithStack(intervals);

        System.out.println("测试用例2");
        for (int[] interval : merged) {
            System.out.println("[" + interval[0] + "," + interval[1] + "]");
        }
    }
}

测试结果

java 复制代码
测试用例1
[1,6]
[8,10]
[15,18]
测试用例2
[1,18]

复杂度分析

这道题的复杂度分析主要取决于排序,这也是整个算法的性能瓶颈。我们可以从时间与空间两个维度来具体拆解:

⏱️ 时间复杂度:O(nlog⁡n)O(n \log n)O(nlogn)

  1. 排序开销 (O(nlog⁡n)O(n \log n)O(nlogn))
    • 算法首先调用了 Arrays.sort() 对区间数组进行排序。在 Java 中,对于原始数据类型或对象数组的排序通常采用优化的快速排序或归并排序,其平均时间复杂度为 O(nlog⁡n)O(n \log n)O(nlogn)。
  2. 扫描合并开销 (O(n)O(n)O(n))
    • 排序完成后,我们只需要对数组进行一次线性遍历。在遍历过程中,对于每个区间,我们只进行一次比较操作(检查与结果列表末尾区间的重叠情况)以及可能的更新操作(修改末尾区间的结束位置)。
    • 这些操作(get、比较、赋值)都是常数时间 O(1)O(1)O(1) 的,遍历 nnn 个元素的总时间就是 O(n)O(n)O(n)。
  3. 总体计算
    • 总时间复杂度 = 排序时间 + 遍历时间 = O(nlog⁡n)+O(n)O(n \log n) + O(n)O(nlogn)+O(n)。
    • 根据大 O 表示法的规则,低阶项和常数系数可以忽略,因此最终的时间复杂度由排序主导,即 O(nlog⁡n)O(n \log n)O(nlogn)

💾 空间复杂度:O(1)O(1)O(1) 或 O(n)O(n)O(n)

空间复杂度的分析取决于我们如何定义"额外空间":

  1. 如果不考虑排序使用的空间

    • 我们只使用了一个 ArrayList 来存储结果。虽然我们在代码中创建了 list,但这是用于存储输出结果的,通常不被视为"额外"的辅助空间。
    • 除此之外,我们只使用了常数个临时变量(如 current, top, newEnd 等)。
    • 因此,在这种计算方式下,额外空间复杂度为 O(1)O(1)O(1)
  2. 如果考虑排序使用的空间

    • Java 的 Arrays.sort() 对于对象(如 int[])的排序,在最坏情况下(虽然现代实现会优化)可能需要 O(log⁡n)O(\log n)O(logn) 到 O(n)O(n)O(n) 的递归栈空间。
    • 此外,如果输入数据不能被修改,我们需要创建副本,这需要 O(n)O(n)O(n) 的空间。
    • 因此,更严谨的总空间复杂度通常是 O(n)O(n)O(n)(主要由排序算法的栈空间或结果存储决定)。

总结

  • 时间复杂度O(nlog⁡n)O(n \log n)O(nlogn)(排序是瓶颈)。
  • 空间复杂度O(1)O(1)O(1)(仅指算法使用的额外变量,不包括结果存储和排序栈空间)。
相关推荐
BothSavage5 小时前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法
小林ixn5 小时前
从暴力到KMP:一道题彻底搞懂字符串匹配的前世今生
算法
烬羽7 小时前
字符串算法入门:从反转字符串到回文判断,面试不再慌
算法·面试
先吃饱再说1 天前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰1 天前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术1 天前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六1 天前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize1 天前
初识DFS 与 BFS:递归、队列与图遍历
算法