我们借助一个辅助链表(元素类型是一维数组)来进行结果统计。
这个算法解决了“合并区间”的问题,具体要求是给定一组区间(每个区间有开始和结束位置),如果两个区间有重叠,那么需要将它们合并成一个区间,并返回所有不重叠的区间。
算法思想:
-
排序区间:
首先,我们将所有区间按照其开始时间进行排序。这是解决问题的关键,因为一旦区间按照开始时间排序后,我们可以轻松地通过遍历来检查相邻区间是否重叠。- 假设我们有区间:
[[1,3], [2,6], [8,10], [15,18]]
。排序后区间变为:[[1,3], [2,6], [8,10], [15,18]]
,这里已经按照开始时间排序好了。
通过排序后,可以保证如果两个区间能够合并,那么它们必然是相邻的,这样我们可以通过一趟遍历完成合并操作。
- 假设我们有区间:
-
合并区间:
在遍历区间时,我们将检查每个区间和之前的区间是否有重叠:- 如果当前区间的开始时间大于上一个区间的结束时间,说明它们不重叠,此时将当前区间添加到结果中。
- 如果当前区间的开始时间小于或等于上一个区间的结束时间,说明它们有重叠,此时将它们合并成一个区间。合并的规则是更新上一个区间的结束时间为两个区间结束时间的最大值。
举个例子:
- 第一个区间
[1,3]
和第二个区间[2,6]
有重叠,因为 2 小于等于 3,因此我们将它们合并为[1,6]
。 - 然后再看区间
[8,10]
和[1,6]
,它们没有重叠(因为 8 大于 6),所以直接将[8,10]
添加到结果中。 - 同理,区间
[15,18]
也不与之前的区间重叠,直接加入结果。
-
返回结果:
遍历所有区间后,我们得到了合并后的区间集合,并将其返回。
算法流程:
- 首先对所有区间按起始位置升序排序。
- 使用一个链表
merged
存储合并后的区间。 - 遍历排序后的区间列表:
- 如果当前区间与已合并的最后一个区间没有重叠,直接将当前区间加入
merged
。 - 如果当前区间与最后一个区间重叠,更新最后一个区间的结束时间为两个区间中结束时间的较大值。
- 如果当前区间与已合并的最后一个区间没有重叠,直接将当前区间加入
- 遍历完成后,将链表转换为数组并返回。
时间复杂度:
- 排序的时间复杂度:
O(n log n)
,其中n
是区间的数量。 - 合并过程的时间复杂度:
O(n)
,因为我们只需要遍历一次区间列表。
因此,整体时间复杂度是 O(n log n)
,其中 n
是区间的数量。
示例:
输入:[[1,3],[2,6],[8,10],[15,18]]
- 首先按起始位置排序,得到:
[[1,3],[2,6],[8,10],[15,18]]
。 - 合并
[1,3]
和[2,6]
,结果是[1,6]
。 8,10
和[1,6]
无重叠,直接添加。15,18
和之前无重叠,直接添加。
输出:[[1,6],[8,10],[15,18]]
通过这个思路,我们能够高效地解决合并区间的问题。
Java 代码
class Solution {
public int[][] merge(int[][] intervals) {
//首先对intervals中的子数组按照开始时间(左区间)进行排序
//Arrays.sort默认使用双轴快速排序, 根据第二个参数提供的lamba表达式定义的比较规则进行排序(升序)
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));//a, b是intervals中的两个元素,也就是2个子数组
//然后初始化一个元素类型为一维数组的链表来存放结果
LinkedList<int[]> merged = new LinkedList<>();
for(int[] interval : intervals) {
//根据merged结果链表中的末尾元素的右区间值和当前循环所指向的子数组的左区间元素进行判断
if(merged.isEmpty() || merged.getLast()[1] < interval[0]) {
//如果结果链表中末尾元素的右区间值小于当前子数组左区间元素值,说明没有重叠,直接加入当前子数组
merged.add(interval);
} else {
//否则,有重叠,不需要添加子数组到结果链表中,仅仅需要更新链表末尾元素的右区间元素值
merged.getLast()[1] = Math.max(merged.getLast()[1], interval[1]);
}
}
//把结果链表转换为数组
return merged.toArray(new int[merged.size()][]);
}
}