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上的问题,还能将这种思想应用到实际开发中的各种场景。

相关推荐
不穿格子的程序员1 天前
从零开始写算法——普通数组篇:缺失的第一个正数
算法·leetcode·哈希算法
带刺的坐椅1 天前
用 10 行 Java8 代码,开发一个自己的 ClaudeCodeCLI?你信吗?
java·ai·llm·agent·solon·mcp·claudecode·skills
Nebula_g1 天前
线程进阶: 无人机自动防空平台开发教程(更新)
java·开发语言·数据结构·学习·算法·无人机
HAPPY酷1 天前
构造与析构:C++ 中对象的温柔生灭
java·jvm·c++
乔江seven1 天前
【Flask 进阶】3 从同步到异步:基于 Redis 任务队列解决 API 高并发与长耗时任务阻塞
redis·python·flask
pchaoda1 天前
基本面因子计算入门
python·matplotlib·量化
lang201509281 天前
Java JSR 250核心注解全解析
java·开发语言
Wpa.wk1 天前
接口自动化测试 - 请求构造和响应断言 -Rest-assure
开发语言·python·测试工具·接口自动化
czhc11400756631 天前
协议 25
java·开发语言·算法
逆光的July1 天前
如何解决超卖问题
java