LeetCode 经典题:“合并区间”

引言

在算法和编程领域,区间操作是一个常见的问题类型。今天,我们将深入探讨一道经典的区间合并问题,并且使用 Java 语言来实现高效的解决方案。这道题不仅能帮助我们提升对数组操作、排序算法以及条件判断逻辑的理解,还能让我们掌握如何运用这些知识来解决实际应用中的复杂问题。

问题描述

给定一个区间的集合 intervals,其中 intervals[i] = [starti, endi]。我们的任务是合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需覆盖输入中的所有区间。例如,输入 intervals = [[1,3],[2,6],[8,10],[15,18]],输出应为 [[1,6],[8,10],[15,18]]。因为 [1,3][2,6] 重叠,合并后变为 [1,6]

解题思路

排序的关键作用

解决这个问题的第一步是对区间进行排序。为什么要排序呢?因为排序后,我们可以保证所有区间按照左端点的顺序排列。这样一来,在后续遍历区间时,相邻的区间更有可能是重叠的,从而大大简化了合并逻辑。我们使用 Java 标准库中的 Arrays.sort 方法来对区间进行排序。排序的依据是区间的左端点,通过 Lambda 表达式 (p, q) -> p[0] - q[0] 来定义排序规则。这里的 pq 代表数组中的两个区间,p[0] - q[0] 表示按照区间左端点进行升序排序。如果 p[0] < q[0],那么 p 区间会排在 q 区间之前。不过需要注意的是,这种简单的减法操作可能会导致整数溢出问题,更安全的做法是使用 Integer.compare(p[0], q[0]) 来替代。

遍历与合并过程

完成排序后,我们开始遍历排序后的区间数组。在遍历过程中,我们使用一个 ArrayList 来存储合并后的区间。对于每一个区间 p,我们首先检查结果列表 ans 是否为空。如果 ans 为空,直接将当前区间 p 添加到 ans 中。如果 ans 不为空,我们比较当前区间 p 的左端点和 ans 中最后一个区间的右端点。如果 p 的左端点小于等于 ans 中最后一个区间的右端点,说明这两个区间有重叠,我们就更新 ans 中最后一个区间的右端点为当前区间 p 的右端点和 ans 中最后一个区间的右端点中的较大值。例如,ans 中最后一个区间是 [1, 3],当前区间 p[2, 6],由于 2 <= 3,所以将 ans 中最后一个区间更新为 [1, 6]。如果 p 的左端点大于 ans 中最后一个区间的右端点,说明这两个区间没有重叠,直接将当前区间 p 添加到 ans 中。

代码实现

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

class Solution {
    public int[][] merge(int[][] intervals) {
        // 输入验证:如果 intervals 为空或长度为 0,直接返回空数组
        if (intervals == null || intervals.length == 0) {
            return new int[0][];
        }
        // 按照左端点从小到大排序
        Arrays.sort(intervals, (p, q) -> Integer.compare(p[0], q[0]));
        List<int[]> ans = new ArrayList<>();
        for (int[] p : intervals) {
            int m = ans.size();
            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);
            }
        }
        return ans.toArray(new int[ans.size()][]);
    }
}

代码解释

  1. 类和方法定义:我们定义了一个名为 Solution 的类,其中包含一个公共方法 merge。这个方法接收一个二维整数数组 intervals 作为参数,并返回一个二维整数数组,即合并后的区间数组。
  2. 输入验证:在方法开始处,我们进行了输入验证。如果 intervals 为空或者长度为 0,直接返回一个空的二维数组,这是为了确保程序在面对非法输入时不会出错,提高代码的健壮性。
  3. 区间排序:使用 Arrays.sort 方法对区间数组进行排序,排序依据是区间左端点。这里使用了 Integer.compare 方法来避免整数溢出问题。
  4. 初始化结果列表:创建一个 ArrayList 类型的 ans 列表,用于存储合并后的区间。ArrayList 可以动态调整大小,方便我们添加和管理区间。
  5. 遍历合并区间:通过增强型 for 循环遍历排序后的区间数组。在循环内部,首先获取 ans 列表的当前大小 m。然后判断当前区间 p 是否可以与 ans 中最后一个区间合并。如果可以合并,使用 Math.max 方法更新 ans 中最后一个区间的右端点。如果不能合并,直接将当前区间 p 添加到 ans 中。
  6. 转换为数组返回:最后,将 ans 列表转换为二维整数数组并返回。通过 ans.toArray(new int[ans.size()][]) 方法实现列表到数组的转换。

复杂度分析

时间复杂度

排序操作的时间复杂度为 O ( n l o g n ) O(n log n) O(nlogn),其中 n n n 是区间的数量。遍历区间数组的时间复杂度为 O ( n ) O(n) O(n)。由于排序操作的时间复杂度在数量级上高于遍历操作,所以整个算法的时间复杂度为 O ( n l o g n ) O(n log n) O(nlogn)

空间复杂度

除了输入和输出所需的空间外,我们额外使用了一个 ArrayList 来存储合并后的区间。在最坏情况下,所有区间都不重叠,ArrayList 中会存储 n n n 个区间,所以空间复杂度为 O ( n ) O(n) O(n)

总结

通过对这道区间合并问题的详细分析和实现,我们学习了如何运用排序和遍历的技巧来解决区间操作问题。在实际编程中,类似的思路可以应用于许多场景,如任务调度、资源分配等。同时,我们也注意到了在实现过程中的一些细节问题,如排序时的整数溢出问题和输入验证。希望通过本文的讲解,读者能够更好地理解和掌握这类问题的解决方法,提升自己的编程能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值