LeetCode 56.合并区间 Java

区间合并问题详解:排序与贪心策略

题目描述

给定一个区间集合,其中每个区间用一对整数 [start, end] 表示,任务是合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需要恰好覆盖原始区间集合中的所有区间。

示例1:

复制代码
输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠,将它们合并为 [1,6]

示例2:

复制代码
输入: intervals = [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间

解题思路分析

这道题的核心在于如何判断和处理区间的重叠。我的解题思路如下:

1. 排序是前提

如果不先对区间进行排序,我们将无法系统性地判断哪些区间可能重叠。想象一下,如果区间是乱序的,我们可能需要多次比较才能确定最终的合并结果。

2. 合并策略

排序后,我们可以按顺序遍历区间,每次将当前区间与已合并区间列表中的最后一个区间进行比较。这样只需要一次遍历就能完成所有合并操作。

详细解法步骤

第一步:区间排序

java 复制代码
// 1.对intervals进行排序(按照子数组的起始做排序)
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

这里使用Java的Arrays.sort方法,通过自定义比较器按照每个区间的起始位置进行升序排序。这是整个算法的基础。

第二步:遍历与合并

java 复制代码
// 2.遍历排序后的intervals
// 2.1 定义一个数组,用来存储合并后的区间
List<int[]> merged = new ArrayList<>();
for(int i=0;i<intervals.length;i++){
    int start = intervals[i][0];
    int end = intervals[i][1];
    
    //如果是第一次来,或者此时的这个区间与merged的最后一个区间没有重叠
    if(merged.isEmpty()||merged.get(merged.size()-1)[1] < start){
        merged.add(new int[]{start,end});
    }else{
        //有重叠,更新merged的最后一个区间的结束位置end
        merged.get(merged.size()-1)[1] = Math.max(intervals[i][1],merged.get(merged.size()-1)[1]);
    }
}

关键判断条件解析:

  • 无重叠情况:当前区间的起始位置 > 已合并区间列表中最后一个区间的结束位置
  • 有重叠情况:当前区间的起始位置 ≤ 已合并区间列表中最后一个区间的结束位置

当检测到重叠时,我们只需要更新已合并区间列表中最后一个区间的结束位置,取两个区间结束位置的较大值。

完整代码实现

java 复制代码
class Solution {
    /**
     * 思路:
     * 1. 先对区间数组进行排序,不排序,无法进行区间合并
     * 2. 准备一个merged来存储合并后的区间,遍历所有区间,比较当前区间与merged中的最后一个区间是否有重叠(重叠的判断:就是这个区间的开头 <= 另外一个区间的结尾)
     *    有重叠则更新merged中最后一个区间的结尾,这个就完成了区间的合并,还保证了不会遗漏
     * @param intervals
     * @return
     */
    public int[][] merge(int[][] intervals) {
        // 1.对intervals进行排序(按照子数组的起始做排序)
        Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

        // 2.遍历排序后的intervals
        // 2.1 定义一个数组,用来存储合并后的区间
        List<int[]> merged = new ArrayList<>();
        for(int i=0;i<intervals.length;i++){
            int start = intervals[i][0];
            int end = intervals[i][1];

            //如果是第一次来,或者此时的这个区间与merged的最后一个区间没有重叠
            if(merged.isEmpty()||merged.get(merged.size()-1)[1] < start){
                merged.add(new int[]{start,end});
            }else{
                //有重叠,更新merged的最后一个区间的结束位置end
                merged.get(merged.size()-1)[1] = Math.max(intervals[i][1],merged.get(merged.size()-1)[1]);
            }
        }

        //返回结果
        return merged.toArray(new int[merged.size()][]);
    }
}

算法复杂度分析

  • 时间复杂度:O(n log n),其中排序操作需要 O(n log n) 时间,遍历区间需要 O(n) 时间
  • 空间复杂度:O(log n) 或 O(n),取决于排序算法的实现。Java中Arrays.sort()使用TimSort,空间复杂度为O(log n)到O(n)。此外,结果存储需要O(n)空间

关键点总结

  1. 排序的必要性:排序使得我们可以按顺序处理区间,确保不会遗漏任何可能的重叠
  2. 贪心策略:每次只考虑当前区间与已合并区间列表中的最后一个区间,这种局部最优选择能导致全局最优解
  3. 重叠判断 :理解 当前区间起始 ≤ 前一个区间结束 是判断重叠的关键条件
  4. 边界处理 :注意区间起始和结束的包含关系,特别是当 [1,4] 和 [4,5] 这种情况时,它们被认为是重叠的

扩展思考

  1. 如果区间是已经排序的,可以如何优化算法?
  2. 如果要求合并后的区间按照某种特定顺序排列,如何处理?
  3. 如果区间数量非常大,无法一次性加载到内存中,应该如何解决?

实际应用场景

区间合并算法在实际中有很多应用:

  • 日程安排和会议室的预订
  • 磁盘空间的分配与回收
  • 网络带宽的时间段分配
  • 任务调度和时间线管理

通过理解和掌握这个算法,你不仅能够解决LeetCode上的问题,还能将这种思想应用到实际开发中的各种场景。

相关推荐
杨杨杨大侠5 小时前
都能接模型、RAG 和 MCP,Java Agent 框架到底差在哪?
java
破茧蝴蝶5 小时前
Python分布式爬虫完整教程
开发语言·python
benchmark_cc5 小时前
量化系统架构设计:为什么应该将数据获取与策略逻辑解耦?
python·数据分析·pandas·量化交易·股票数据·quantdash
凤山老林6 小时前
Spring Boot 启动冲刺:耗时剖析、懒加载策略与事件驱动初始化优化
spring boot·后端·python
小白快快跑哦6 小时前
python-字符串全解(六):正则表达式-逻辑运算或
python·正则表达式·字符串·逻辑运算
Thinker QAQ6 小时前
并发编程(九):BlockingQueue 与 Channel——语言层的原子性、可见性与有序性
java·python·go·并发编程·channel·blockingqueue·message passing
hanlin036 小时前
刷题笔记:力扣第32题-最长有效括号
笔记·算法·leetcode
道满科技6 小时前
Function Calling 参数总出错?只修参数就行
linux·windows·python
All for pursuit.7 小时前
【动态规划-21】312.戳气球
数据结构·c++·算法·leetcode·动态规划
霸道流氓气质7 小时前
RAG 可观测性与生产监控实战:四层遥测模型、五大仪表盘与 OpenTelemetry 全链路追踪的 Java 生产级实现
java·人工智能·python