区间合并问题详解:排序与贪心策略
题目描述
给定一个区间集合,其中每个区间用一对整数 [start, end] 表示,任务是合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需要恰好覆盖原始区间集合中的所有区间。
示例1:
输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠,将它们合并为 [1,6]
示例2:
输入: intervals = [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间
解题思路分析
这道题的核心在于如何判断和处理区间的重叠。我的解题思路如下:
1. 排序是前提
如果不先对区间进行排序,我们将无法系统性地判断哪些区间可能重叠。想象一下,如果区间是乱序的,我们可能需要多次比较才能确定最终的合并结果。
2. 合并策略
排序后,我们可以按顺序遍历区间,每次将当前区间与已合并区间列表中的最后一个区间进行比较。这样只需要一次遍历就能完成所有合并操作。
详细解法步骤
第一步:区间排序
java
// 1.对intervals进行排序(按照子数组的起始做排序)
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
这里使用Java的Arrays.sort方法,通过自定义比较器按照每个区间的起始位置进行升序排序。这是整个算法的基础。
第二步:遍历与合并
java
// 2.遍历排序后的intervals
// 2.1 定义一个数组,用来存储合并后的区间
List<int[]> merged = new ArrayList<>();
for(int i=0;i<intervals.length;i++){
int start = intervals[i][0];
int end = intervals[i][1];
//如果是第一次来,或者此时的这个区间与merged的最后一个区间没有重叠
if(merged.isEmpty()||merged.get(merged.size()-1)[1] < start){
merged.add(new int[]{start,end});
}else{
//有重叠,更新merged的最后一个区间的结束位置end
merged.get(merged.size()-1)[1] = Math.max(intervals[i][1],merged.get(merged.size()-1)[1]);
}
}
关键判断条件解析:
- 无重叠情况:当前区间的起始位置 > 已合并区间列表中最后一个区间的结束位置
- 有重叠情况:当前区间的起始位置 ≤ 已合并区间列表中最后一个区间的结束位置
当检测到重叠时,我们只需要更新已合并区间列表中最后一个区间的结束位置,取两个区间结束位置的较大值。
完整代码实现
java
class Solution {
/**
* 思路:
* 1. 先对区间数组进行排序,不排序,无法进行区间合并
* 2. 准备一个merged来存储合并后的区间,遍历所有区间,比较当前区间与merged中的最后一个区间是否有重叠(重叠的判断:就是这个区间的开头 <= 另外一个区间的结尾)
* 有重叠则更新merged中最后一个区间的结尾,这个就完成了区间的合并,还保证了不会遗漏
* @param intervals
* @return
*/
public int[][] merge(int[][] intervals) {
// 1.对intervals进行排序(按照子数组的起始做排序)
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
// 2.遍历排序后的intervals
// 2.1 定义一个数组,用来存储合并后的区间
List<int[]> merged = new ArrayList<>();
for(int i=0;i<intervals.length;i++){
int start = intervals[i][0];
int end = intervals[i][1];
//如果是第一次来,或者此时的这个区间与merged的最后一个区间没有重叠
if(merged.isEmpty()||merged.get(merged.size()-1)[1] < start){
merged.add(new int[]{start,end});
}else{
//有重叠,更新merged的最后一个区间的结束位置end
merged.get(merged.size()-1)[1] = Math.max(intervals[i][1],merged.get(merged.size()-1)[1]);
}
}
//返回结果
return merged.toArray(new int[merged.size()][]);
}
}
算法复杂度分析
- 时间复杂度:O(n log n),其中排序操作需要 O(n log n) 时间,遍历区间需要 O(n) 时间
- 空间复杂度:O(log n) 或 O(n),取决于排序算法的实现。Java中Arrays.sort()使用TimSort,空间复杂度为O(log n)到O(n)。此外,结果存储需要O(n)空间
关键点总结
- 排序的必要性:排序使得我们可以按顺序处理区间,确保不会遗漏任何可能的重叠
- 贪心策略:每次只考虑当前区间与已合并区间列表中的最后一个区间,这种局部最优选择能导致全局最优解
- 重叠判断 :理解
当前区间起始 ≤ 前一个区间结束是判断重叠的关键条件 - 边界处理 :注意区间起始和结束的包含关系,特别是当
[1,4]和[4,5]这种情况时,它们被认为是重叠的
扩展思考
- 如果区间是已经排序的,可以如何优化算法?
- 如果要求合并后的区间按照某种特定顺序排列,如何处理?
- 如果区间数量非常大,无法一次性加载到内存中,应该如何解决?
实际应用场景
区间合并算法在实际中有很多应用:
- 日程安排和会议室的预订
- 磁盘空间的分配与回收
- 网络带宽的时间段分配
- 任务调度和时间线管理
通过理解和掌握这个算法,你不仅能够解决LeetCode上的问题,还能将这种思想应用到实际开发中的各种场景。