1. 问题分析与理解
LeetCode 986题"Interval List Intersections"是一个典型的区间处理问题,要求我们找出两个已排序区间列表的交集。这个问题在实际开发中有着广泛的应用场景,比如时间调度冲突检测、日历应用中的空闲时间查找等。
给定两个区间列表firstList和secondList,其中每个区间列表都是互不相交且按起始端点排序的。我们需要返回这两个列表中所有相交区间的集合。这里的相交区间指的是两个区间有重叠的部分,例如[1,3]和[2,4]的交集是[2,3]。
1.1 输入输出示例分析
让我们通过几个示例来更好地理解这个问题:
输入: firstList = [[0,2],[5,10],[13,23],[24,25]] secondList = [[1,5],[8,12],[15,24],[25,26]]
输出: [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
这个示例展示了多种不同的交集情况:
- 部分重叠:[0,2]和[1,5] → [1,2]
- 单点重叠:[5,10]和[1,5] → [5,5]
- 完全包含:[13,23]和[15,24] → [15,23]
- 端点重叠:[24,25]和[15,24] → [24,24]
1.2 关键问题分解
解决这个问题需要考虑以下几个关键点:
- 如何高效地遍历两个区间列表
- 如何判断两个区间是否有交集
- 如何计算两个区间的交集
- 如何管理结果集的存储和返回
2. 算法设计与思路
2.1 双指针法
这个问题最适合使用双指针法来解决。我们维护两个指针i和j,分别指向firstList和secondList的当前区间。通过比较当前两个区间的相对位置关系,我们可以决定移动哪个指针。
算法步骤如下:
- 初始化i=0, j=0
- 当i < firstList长度且j < secondList长度时循环: a. 检查firstList[i]和secondList[j]是否有交集 b. 如果有交集,计算并记录交集 c. 移动结束较早的那个区间的指针
- 返回所有找到的交集
2.2 区间相交判断
判断两个区间[a,b]和[c,d]是否有交集,可以通过以下条件:
- 如果b < c或d < a,则无交集
- 否则有交集,交集为[max(a,c), min(b,d)]
这个判断条件可以简化为:如果max(a,c) <= min(b,d),则有交集。
2.3 指针移动策略
决定移动哪个指针是关键。我们应该移动结束点较小的那个区间的指针,因为结束点较大的区间可能与后面的区间还有交集。
例如: firstList[i] = [1,5] secondList[j] = [2,6] 交集是[2,5] 因为5 < 6,所以移动i指针
3. C语言实现详解
3.1 数据结构定义
首先我们需要定义区间结构体和返回结果的结构:
typedef struct { int start; int end; } Interval; typedef struct { Interval* intervals; int size; int capacity; } Result;3.2 核心算法实现
int min(int a, int b) { return a < b ? a : b; } int max(int a, int b) { return a > b ? a : b; } Interval* intervalIntersection(Interval* firstList, int firstListSize, Interval* secondList, int secondListSize, int* returnSize) { Interval* result = malloc(sizeof(Interval) * (firstListSize + secondListSize)); int count = 0; int i = 0, j = 0; while (i < firstListSize && j < secondListSize) { Interval a = firstList[i]; Interval b = secondList[j]; // 检查是否有交集 int start = max(a.start, b.start); int end = min(a.end, b.end); if (start <= end) { result[count].start = start; result[count].end = end; count++; } // 移动指针 if (a.end < b.end) { i++; } else { j++; } } *returnSize = count; return result; }3.3 内存管理注意事项
在C语言中,我们需要特别注意内存管理:
- 预先分配足够大的结果数组(最坏情况下大小为两个列表长度之和)
- 调用者需要负责释放返回的结果数组
- 不需要修改输入的两个区间列表
4. 复杂度分析与优化
4.1 时间复杂度
算法的时间复杂度是O(m+n),其中m和n分别是两个区间列表的长度。这是因为每个区间最多被访问一次,且每次比较后至少有一个指针会移动。
4.2 空间复杂度
空间复杂度是O(m+n)用于存储结果。在实际应用中,如果结果可以就地存储或使用其他方式输出,可以优化到O(1)的额外空间。
4.3 可能的优化方向
- 动态调整结果数组大小:开始时分配较小空间,根据需要realloc
- 如果内存紧张,可以先遍历一次计算最终结果大小,再精确分配
- 对于特别大的数据集,可以考虑分批处理
5. 边界条件与测试用例
5.1 常见边界情况
- 空输入列表
- 完全没有交集的列表
- 所有区间都相交的列表
- 单点区间(如[5,5])
- 大范围的区间包含多个小区间
5.2 测试用例设计
void test_case1() { Interval firstList[] = {{0,2},{5,10},{13,23},{24,25}}; Interval secondList[] = {{1,5},{8,12},{15,24},{25,26}}; int returnSize; Interval* result = intervalIntersection(firstList, 4, secondList, 4, &returnSize); // 验证结果... free(result); } void test_empty_list() { Interval firstList[] = {{1,3},{5,9}}; Interval secondList[] = {}; int returnSize; Interval* result = intervalIntersection(firstList, 2, secondList, 0, &returnSize); assert(returnSize == 0); free(result); }6. 实际应用与扩展
6.1 实际应用场景
- 日历应用:查找多个用户的共同空闲时间
- 资源调度:检测资源使用时间冲突
- 基因组学:查找DNA序列的重叠区域
- 项目管理:识别任务时间线的重叠部分
6.2 问题变种
- 合并区间列表而不是找交集
- 查找多个(超过两个)区间列表的交集
- 在允许一定误差范围内查找交集
- 处理未排序的区间列表
6.3 扩展思考
如果需要处理动态变化的区间列表,可以考虑使用区间树或其他高级数据结构来优化查询效率。对于特别大的数据集,可能需要考虑分布式算法来处理。