☰
LeetCode 986题解析:双指针法解决区间交集问题
2026/11/4 5:37:28 网站建设 项目流程

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 关键问题分解

解决这个问题需要考虑以下几个关键点:

  1. 如何高效地遍历两个区间列表
  2. 如何判断两个区间是否有交集
  3. 如何计算两个区间的交集
  4. 如何管理结果集的存储和返回

2. 算法设计与思路

2.1 双指针法

这个问题最适合使用双指针法来解决。我们维护两个指针i和j,分别指向firstList和secondList的当前区间。通过比较当前两个区间的相对位置关系,我们可以决定移动哪个指针。

算法步骤如下:

  1. 初始化i=0, j=0
  2. 当i < firstList长度且j < secondList长度时循环: a. 检查firstList[i]和secondList[j]是否有交集 b. 如果有交集,计算并记录交集 c. 移动结束较早的那个区间的指针
  3. 返回所有找到的交集

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语言中,我们需要特别注意内存管理:

  1. 预先分配足够大的结果数组(最坏情况下大小为两个列表长度之和)
  2. 调用者需要负责释放返回的结果数组
  3. 不需要修改输入的两个区间列表

4. 复杂度分析与优化

4.1 时间复杂度

算法的时间复杂度是O(m+n),其中m和n分别是两个区间列表的长度。这是因为每个区间最多被访问一次,且每次比较后至少有一个指针会移动。

4.2 空间复杂度

空间复杂度是O(m+n)用于存储结果。在实际应用中,如果结果可以就地存储或使用其他方式输出,可以优化到O(1)的额外空间。

4.3 可能的优化方向

  1. 动态调整结果数组大小:开始时分配较小空间,根据需要realloc
  2. 如果内存紧张,可以先遍历一次计算最终结果大小,再精确分配
  3. 对于特别大的数据集,可以考虑分批处理

5. 边界条件与测试用例

5.1 常见边界情况

  1. 空输入列表
  2. 完全没有交集的列表
  3. 所有区间都相交的列表
  4. 单点区间(如[5,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 实际应用场景

  1. 日历应用:查找多个用户的共同空闲时间
  2. 资源调度:检测资源使用时间冲突
  3. 基因组学:查找DNA序列的重叠区域
  4. 项目管理:识别任务时间线的重叠部分

6.2 问题变种

  1. 合并区间列表而不是找交集
  2. 查找多个(超过两个)区间列表的交集
  3. 在允许一定误差范围内查找交集
  4. 处理未排序的区间列表

6.3 扩展思考

如果需要处理动态变化的区间列表,可以考虑使用区间树或其他高级数据结构来优化查询效率。对于特别大的数据集,可能需要考虑分布式算法来处理。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询