1. 区间交集问题解析
区间交集是算法面试和编程竞赛中的经典问题,也是华为OD机考中的高频考点。这类问题考察的是对数据结构的基本操作能力和边界条件处理能力。在实际开发中,类似逻辑广泛应用于日程安排、资源分配等场景。
1.1 问题核心理解
给定一组闭区间,我们需要:
- 找出所有两两相交的区间对
- 计算它们的公共区间
- 将这些公共区间合并(如果它们之间有重叠)
- 最终输出合并后的有序区间列表
关键点在于:
- 如何高效判断两个区间是否相交
- 如何计算两个区间的交集
- 如何合并有重叠的区间
1.2 区间运算基本规则
对于两个区间 [a1, a2] 和 [b1, b2]:
- 相交条件:a1 ≤ b2 且 b1 ≤ a2
- 交集计算:[max(a1, b1), min(a2, b2)]
- 合并条件:如果两个区间有重叠(即一个区间的起点 ≤ 另一个区间的终点)
- 合并结果:[min(a1, b1), max(a2, b2)]
注意:单个区间视为没有公共区间,直接跳过处理
2. 解题思路与算法设计
2.1 暴力解法分析
最直观的方法是双重循环:
- 遍历所有区间对
- 计算每对区间的交集
- 将所有交集收集起来
- 对这些交集进行合并
时间复杂度:O(n²) + O(m log m)(m是交集数量)
空间复杂度:O(m)
这种解法在小数据量时可行,但当n=1000时,n²=1,000,000次操作,可能不够高效。
2.2 优化思路
观察到合并后的区间列表需要有序输出,我们可以先对原始区间排序。这样:
- 排序后相邻区间更可能有交集
- 合并操作可以线性完成
具体步骤:
- 按区间起点排序
- 初始化一个空的结果列表
- 遍历排序后的区间:
- 如果当前区间与结果列表中最后一个区间有重叠,则合并
- 否则直接加入结果列表
2.3 算法选择
对于本题的特殊要求(先找所有交集再合并),可以采用以下策略:
- 计算所有两两区间的交集
- 对这些交集区间进行排序
- 合并重叠的交集区间
3. Java实现详解
3.1 数据结构定义
java复制class Interval {
int start;
int end;
public Interval(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public String toString() {
return "[" + start + "," + end + "]";
}
}
3.2 核心算法实现
java复制import java.util.*;
public class IntervalIntersection {
// 判断两个区间是否相交
private static boolean isOverlap(Interval a, Interval b) {
return a.start <= b.end && b.start <= a.end;
}
// 计算两个区间的交集
private static Interval getIntersection(Interval a, Interval b) {
if (!isOverlap(a, b)) return null;
return new Interval(Math.max(a.start, b.start), Math.min(a.end, b.end));
}
// 合并区间列表
private static List<Interval> mergeIntervals(List<Interval> intervals) {
if (intervals.isEmpty()) return intervals;
intervals.sort((a, b) -> a.start - b.start);
List<Interval> merged = new ArrayList<>();
merged.add(intervals.get(0));
for (int i = 1; i < intervals.size(); i++) {
Interval last = merged.get(merged.size() - 1);
Interval current = intervals.get(i);
if (current.start <= last.end) {
// 合并区间
last.end = Math.max(last.end, current.end);
} else {
merged.add(current);
}
}
return merged;
}
public static List<Interval> getMergedIntersections(List<Interval> intervals) {
List<Interval> intersections = new ArrayList<>();
int n = intervals.size();
// 计算所有两两区间的交集
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
Interval intersection = getIntersection(intervals.get(i), intervals.get(j));
if (intersection != null) {
intersections.add(intersection);
}
}
}
// 合并这些交集区间
return mergeIntervals(intersections);
}
}
3.3 输入输出处理
java复制public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
List<Interval> intervals = new ArrayList<>();
for (int i = 0; i < n; i++) {
int start = scanner.nextInt();
int end = scanner.nextInt();
intervals.add(new Interval(start, end));
}
List<Interval> result = getMergedIntersections(intervals);
for (Interval interval : result) {
System.out.println(interval);
}
}
4. 复杂度分析与优化
4.1 时间复杂度
- 计算所有两两交集:O(n²)
- 合并区间:O(m log m)(m是交集数量,最坏情况下m=O(n²))
总时间复杂度:O(n² + m log m)
4.2 空间复杂度
需要存储所有交集区间:O(m)
4.3 可能的优化方向
- 提前终止:如果某个区间与后续所有区间都不相交,可以提前终止内层循环
- 分治法:将区间分成多个组,分别处理后再合并结果
- 区间树:使用更高级的数据结构来加速区间查询
5. 测试用例与边界条件
5.1 常规测试用例
输入:
code复制4
1 3
2 6
8 10
15 18
输出:
code复制[2,3]
[8,10]
[15,18]
5.2 边界条件测试
- 空输入:
code复制0
预期输出:(无输出)
- 单个区间:
code复制1
1 5
预期输出:(无输出,因为单个区间无公共区间)
- 完全重叠区间:
code复制3
1 5
2 4
3 6
预期输出:
code复制[2,4]
[3,5]
- 负数和零:
code复制3
-5 -2
-3 0
-1 4
预期输出:
code复制[-3,-2]
[-1,0]
6. 常见问题与调试技巧
6.1 常见错误
- 忘记处理空输入或单个区间的情况
- 区间合并时没有更新正确的终点值
- 输入区间未排序导致合并错误
- 整数溢出(虽然本题限制在-10000到10000)
6.2 调试建议
- 打印中间结果:在计算交集和合并时打印中间状态
- 单元测试:为每个辅助方法(如isOverlap、getIntersection)编写测试用例
- 可视化:绘制区间图帮助理解
关键检查点:
- 确保相交判断逻辑正确
- 合并操作要同时考虑起点和终点
- 输出结果必须有序
7. 实际应用与扩展
7.1 实际应用场景
- 会议室预定系统:查找可用的会议室时间段
- 基因序列比对:寻找DNA序列的重叠区域
- 物流调度:计算车辆可用时间段的交集
7.2 问题变种
- 区间并集:合并所有重叠的原始区间
- 区间差集:找出不在任何其他区间内的部分
- 最大重叠数:找出被最多区间覆盖的点
7.3 性能优化实践
对于大规模数据(n>10000),可以考虑:
- 使用并行计算处理不同的区间对
- 采用空间分区技术减少不必要的比较
- 使用线段树等高级数据结构
在实现这类算法时,我通常会先确保基础版本正确,然后再考虑优化。过早优化往往会导致代码复杂且容易出错。对于机考场景,清晰正确的实现比极致优化更重要。