引言
在算法和编程领域,区间操作是一个常见的问题类型。今天,我们将深入探讨一道经典的区间合并问题,并且使用 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]
来定义排序规则。这里的 p
和 q
代表数组中的两个区间,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()][]);
}
}
代码解释
- 类和方法定义:我们定义了一个名为
Solution
的类,其中包含一个公共方法merge
。这个方法接收一个二维整数数组intervals
作为参数,并返回一个二维整数数组,即合并后的区间数组。 - 输入验证:在方法开始处,我们进行了输入验证。如果
intervals
为空或者长度为 0,直接返回一个空的二维数组,这是为了确保程序在面对非法输入时不会出错,提高代码的健壮性。 - 区间排序:使用
Arrays.sort
方法对区间数组进行排序,排序依据是区间左端点。这里使用了Integer.compare
方法来避免整数溢出问题。 - 初始化结果列表:创建一个
ArrayList
类型的ans
列表,用于存储合并后的区间。ArrayList
可以动态调整大小,方便我们添加和管理区间。 - 遍历合并区间:通过增强型
for
循环遍历排序后的区间数组。在循环内部,首先获取ans
列表的当前大小m
。然后判断当前区间p
是否可以与ans
中最后一个区间合并。如果可以合并,使用Math.max
方法更新ans
中最后一个区间的右端点。如果不能合并,直接将当前区间p
添加到ans
中。 - 转换为数组返回:最后,将
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)。
总结
通过对这道区间合并问题的详细分析和实现,我们学习了如何运用排序和遍历的技巧来解决区间操作问题。在实际编程中,类似的思路可以应用于许多场景,如任务调度、资源分配等。同时,我们也注意到了在实现过程中的一些细节问题,如排序时的整数溢出问题和输入验证。希望通过本文的讲解,读者能够更好地理解和掌握这类问题的解决方法,提升自己的编程能力。