题目介绍
力扣56题:https://leetcode-cn.com/problems/merge-intervals/
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
分析
要判断两个区间[a1, b1], [a2, b2]是否可以合并,其实就是判断是否有a1 <= a2 <= b1,或者a2 <= a1 <= b2。也就是说,如果某个子区间的左边界在另一子区间内,那么它们可以合并。
解决方法:排序
一个简单的想法是,我们可以遍历每一个子区间,然后判断它跟其它区间是否可以合并。如果某两个区间可以合并,那么就把它们合并之后,再跟其它区间去做判断。很明显,这样的暴力算法,时间复杂度不会低于O(n^2)。有没有更好的方式呢?
这里我们发现,判断区间是否可以合并的关键,在于它们左边界的大小关系。所以我们可以先把所有区间,按照左边界进行排序。
那么在排完序的列表中,可以合并的区间一定是连续的。如下图所示,标记为蓝色、黄色和绿色的区间分别可以合并成一个大区间,它们在排完序的列表中是连续的:
代码演示如下:
public class MergeIntervals {
public int[][] merge(int[][] intervals) {
List<int[]> result = new ArrayList<int[]>();
// 先对原数组按左边界排序
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0] - o2[0];
}
});
// 遍历排序后的数组,逐个判断合并
for ( int[] interval: intervals ){
int left = interval[0], right = interval[1];
int length = result.size();
if ( length == 0 || left > result.get(length - 1)[1] ){
result.add(interval);
} else {
int mergedLeft = result.get(length - 1)[0];
int mergedRight = Math.max( result.get(length - 1)[1], right );
result.set( length - 1, new int[]{mergedLeft, mergedRight} );
}
}
return result.toArray(new int[result.size()][]);
}
}
复杂度分析
- 时间复杂度:O(nlogn),其中 n 为区间的数量。除去排序的开销,我们只需要一次线性扫描,所以主要的时间开销是排序的O(nlogn)。
- 空间复杂度:O(logn),其中 n 为区间的数量。O(logn) 即为快速排序所需要的空间复杂度(递归栈深度)。