leetcode 56 给出一个区间的集合,请合并所有重叠的区间

本文介绍了LeetCode第56题的解决方案,通过排序和迭代,合并所有重叠的区间。算法首先按左端点升序排序区间,然后遍历并合并相邻的重叠区间,最后分析了算法的时间复杂度和空间复杂度。
package leetcode.sort;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* @Description: 给出一个区间的集合,请合并所有重叠的区间。

示例 1:

输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:

输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
 
* @Param:  
* @return:  
* @Author: lvhong
* @Date:  
* @E-mail lvhong282@163.com
*/ 
public class lab56 {
    /** 
    * @Description:    //注:此想法来自leetcode 用户 powcai 简化版的排序实现 创建了一个新的ArrayList对原始数据进行排序,之后通过判断两个区间之间左右区间的大小比值来确定有没有重合,将合并后的数组放在一个新的数组内部
    * @Param:  
    * @return:  
    * @Author: lvhong
    * @Date:  
    * @E-mail lvhong282@163.com
    */
    public int[][] merge(int[][] intervals) {
        List<int []> res = new ArrayList<>();
        if(intervals.length<2||intervals==null) return intervals;
        Arrays.sort(intervals, (a, b) -> a[0] - b[0]);     //这是本地的lamada优化
//这是一个lamada表达式,表示将二维数组内的元素大小排序  //原作者的源码
//        Arrays.sort(arr,new Comparator<int[]>(){
//            @Override
//            public int compare(int[] a,int[] b){
//                return a[0]-b[0];
//            }
//        });
        int i=0;
        while(i<intervals.length){
            int left = intervals[i][0];
            int right = intervals[i][1];
            while(i<intervals.length-1 && right>=intervals[i+1][0]){
                i++;
                right=Math.max(right,intervals[i][1]);  //此处判断当前子数组的县一个数组的左值小于当前右值(既重叠)比较两个右值
            }
            res.add(new int[]{left,right});
            i++;
        }
        return res.toArray(new int[0][]);
    }
}

 

官方解法
思路

如果我们按照区间的左端点排序,那么在排完序的列表中,可以合并的区间一定是连续的。如下图所示,标记为蓝色、黄色和绿色的区间分别可以合并成一个大区间,它们在排完序的列表中是连续的:

56-2.png

算法

我们用数组 merged 存储最终的答案。

首先,我们将列表中的区间按照左端点升序排序。然后我们将第一个区间加入 merged 数组中,并按顺序依次考虑之后的每个区间:

如果当前区间的左端点在数组 merged 中最后一个区间的右端点之后,那么它们不会重合,我们可以直接将这个区间加入数组 merged 的末尾;

否则,它们重合,我们需要用当前区间的右端点更新数组 merged 中最后一个区间的右端点,将其置为二者的较大值。

正确性证明

上述算法的正确性可以用反证法来证明:在排完序后的数组中,两个本应合并的区间没能被合并,那么说明存在这样的三元组 (i, j, k)(i,j,k) 以及数组中的三个区间 a[i], a[j], a[k]a[i],a[j],a[k] 满足 i < j < ki<j<k 并且 (a[i], a[k])(a[i],a[k]) 可以合并,但 (a[i], a[j])(a[i],a[j]) 和 (a[j], a[k])(a[j],a[k]) 不能合并。这说明它们满足下面的不等式:

a[i].end < a[j].start \quad (a[i] \text{ 和 } a[j] \text{ 不能合并}) \\ a[j].end < a[k].start \quad (a[j] \text{ 和 } a[k] \text{ 不能合并}) \\ a[i].end \geq a[k].start \quad (a[i] \text{ 和 } a[k] \text{ 可以合并}) \\
a[i].end<a[j].start(a[i] 和 a[j] 不能合并)
a[j].end<a[k].start(a[j] 和 a[k] 不能合并)
a[i].end≥a[k].start(a[i] 和 a[k] 可以合并)

我们联立这些不等式(注意还有一个显然的不等式 a[j].start \leq a[j].enda[j].start≤a[j].end),可以得到:

a[i].end < a[j].start \leq a[j].end < a[k].start
a[i].end<a[j].start≤a[j].end<a[k].start

产生了矛盾!这说明假设是不成立的。因此,所有能够合并的区间都必然是连续的。

 

class Solution {
    public int[][] merge(int[][] intervals) {
        List<int[]> res = new ArrayList<>();
        if (intervals.length == 0 || intervals == null) return res.toArray(new int[0][]);
        // 对起点终点进行排序
        Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
        int i = 0;
        while (i < intervals.length) {
            int left = intervals[i][0];
            int right = intervals[i][1];
            // 如果有重叠,循环判断哪个起点满足条件
            while (i < intervals.length - 1 && intervals[i + 1][0] <= right) {
                i++;
                right = Math.max(right, intervals[i][1]);
            }
            // 将现在的区间放进res里面
            res.add(new int[]{left, right});
            // 接着判断下一个区间
            i++;
        }
        return res.toArray(new int[0][]);
    }
}

复杂度分析

时间复杂度:O(nlogn),其中 n 为区间的数量。除去排序的开销,我们只需要一次线性扫描,所以主要的时间开销是排序的 O(nlogn)。

空间复杂度:O(logn),其中 nn 为区间的数量。这里计算的是存储答案之外,使用的额外空间。O(logn) 即为排序所需要的空间复杂度。

 

原题地址:

https://leetcode-cn.com/problems/merge-intervals/solution/he-bing-qu-jian-by-leetcode-solution/

<think>好的,用户让我介绍合并重叠区间的Java解法,这道题是LeetCode上的经典题目。用户可能正在准备面试或者刷题,需要详细的思路和步骤,而不仅仅是代码。他之前问过intervals[0]的含义,现在这个问题是进阶,可能他在处理二维数组时遇到了困难。 首先,我得确保自己正确理解题目要求。合并所有重叠区间,返回不重叠的数组,且覆盖所有输入。比如输入是[[1,3],[2,6],[8,10]],合并后应该是[[1,6],[8,10]]。那如何判断区间是否重叠呢?如果一个区间的结束时间大于等于下一个区间的开始时间,说明有重叠,需要合并。 接下来,我需要考虑解题步骤。通常这类区间合并问题,排序是第一步。按照区间的起始点进行排序,这样相邻的区间更容易判断是否重叠。排序之后,可以逐个比较当前区间和结果列表中的最后一个区间,决定合并还是添加。 然后,具体的实现细节。比如,如何排序?可以用Java的Arrays.sort配合自定义Comparator。对于每个区间,需要比较当前区间的start和上一个区间的end。如果当前区间的start小于等于上一个区间的end,说明有重叠合并后的end取两者的最大值。否则,直接添加到结果列表。 还要考虑边界情况,比如空输入,或者只有一个区间的情况。这时候直接返回原数组即可。另外,测试用例可能有多个重叠区间需要连续合并,比如多个区间依次重叠,这时候需要不断更新end的值。 用户可能需要详细的步骤解释,所以得拆分成清晰的步骤,比如排序、遍历、合并条件、结果处理。同时,代码部分需要注释清楚,让用户能理解每部分的作用。可能还需要举例说明,比如用例子展示每一步的过程,这样更直观。 另外,用户可能对二维数组的操作不太熟悉,比如如何访问start和end,如何创建结果列表,转换为数组等。所以在代码中要明确写出intervals[i][0]和intervals[i][1],以及使用ArrayList来存储结果,最后转换为int[][]。 最后,总结时间复杂度和空间复杂度。排序的时间复杂度是O(n log n),遍历是O(n),所以总的是O(n log n)。空间复杂度主要是排序所需的O(log n)(如果使用快速排序的话)和结果列表的O(n)。 需要避免的错误点包括:没有先排序导致无法正确合并,或者在合并时没有正确更新end的最大值。比如,可能存在当前区间的end比合并后的end小的情况,这时候要取两者较大的那个。 可能用户之前对intervals[0]的理解已经解决了,现在需要综合运用这些知识来处理整个数组。因此,在思路讲解时,可以提到如何访问每个子数组的起始和结束值,以及如何操作这些值进行合并。 总结来说,解题思路分为排序、遍历合并两个主要步骤,关键是排序后的逐个比较和合并。代码实现时要注意边界情况和数据结构的选择,确保正确高效。</think>以下是解决合并重叠区间问题的详细思路和Java实现: --- ### 一、核心思路 #### 1. 排序预处理 将所有区间按**起始点(start)**升序排序,使相邻的区间更容易判断是否重叠 #### 2. 合并策略 逐个遍历排序后的区间,通过比较当前区间的start与结果列表中最后一个区间的end: - **可合并**:当 `当前区间的start ≤ 结果最后一个区间的end` 时,更新合并后的end为两者的最大值 - **不可合并**:直接将当前区间加入结果列表 --- ### 二、详细步骤分解 1. **边界处理**:当数组为空或只有一个区间时直接返回 2. **排序区间**:按start升序排列 3. **初始化结果列表**:放入第一个区间作为基准 4. **遍历合并**: - 从第二个区间开始遍历 - 比较当前区间start与结果最后一个区间的end - 根据比较结果决定合并或直接添加 5. **结果转换**:将ArrayList转为二维数组 --- ### 三、Java代码实现 ```java import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; class Solution { public int[][] merge(int[][] intervals) { // 处理空输入 if (intervals == null || intervals.length == 0) { return new int[0][]; } // 按区间起点排序 Arrays.sort(intervals, new Comparator<int[]>() { public int compare(int[] a, int[] b) { return a[0] - b[0]; } }); ArrayList<int[]> merged = new ArrayList<>(); merged.add(intervals[0]); // 放入第一个区间 for (int i = 1; i < intervals.length; i++) { int[] last = merged.get(merged.size() - 1); int[] current = intervals[i]; if (current[0] <= last[1]) { // 合并区间,更新end为最大值 last[1] = Math.max(last[1], current[1]); } else { // 无法合并,直接添加 merged.add(current); } } // 转换为二维数组 return merged.toArray(new int[merged.size()][]); } } ``` --- ### 四、关键点解析 #### 1. 排序的重要性 排序使得所有可能重叠区间必然相邻,例如: ``` 原始输入:[[1,3],[2,6],[8,10],[15,18]] → 排序后不变 合并过程: 比较 [1,3] 与 [2,6] → 合并为 [1,6] 比较 [1,6] 与 [8,10] → 不合并 比较 [8,10] 与 [15,18] → 不合并 最终结果:[[1,6],[8,10],[15,18]] ``` #### 2. 合并逻辑的数学保证 当 `current[0] <= last[1]` 时,由于数组已排序,必然满足: - `current[0] >= last[0]`(排序保证) - 合并后的新区间为 `[last[0], max(last[1], current[1])]` --- ### 五、复杂度分析 - **时间复杂度**:$O(n \log n)$ - 排序耗时 $O(n \log n)$ - 线性扫描耗时 $O(n)$ - **空间复杂度**:$O(\log n)$(排序所需栈空间) --- ### 六、测试用例演示 #### 输入:`[[1,3],[2,6],[8,10],[15,18]]` 1. 排序后保持原顺序 2. 合并过程: - 初始结果列表:`[[1,3]]` - 处理`[2,6]`:`2 ≤ 3` → 合并为`[1,6]` - 处理`[8,10]`:`8 > 6` → 直接添加 - 处理`[15,18]`:`15 > 10` → 直接添加 3. 最终结果:`[[1,6],[8,10],[15,18]]` --- ### 七、常见问题解答 #### Q1:为什么必须排序? 无序情况下无法保证相邻区间重叠检查的完备性,例如: ``` 未排序输入:[[1,5],[2,4],[6,7],[5,8]] 正确合并结果应为:[[1,5],[5,8]] → 但如果不排序,可能无法合并[1,5]和[5,8] ``` #### Q2:如何处理完全包含的情况? 例如`[1,5]`和`[2,3]`: - 合并逻辑会自动处理,因为`max(5,3) = 5` - 合并结果为`[1,5]` --- 掌握这个解法后,你可以解决LeetCode 56题(Merge Intervals)及其变种问题。关键要理解排序带来的结构优化和合并条件的数学原理。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值