代表性面试主题

编程面试:如何求两组已排序区间的交集?

编程题中等
Offer.cc 编辑团队发布 更新

题干

给定两组按起点排序、组内互不重叠的闭区间,请返回两组区间的所有交集。说明双指针解法、正确性、复杂度与测试边界。

题干与适用场景

给定两个数组 AB。每个元素是闭区间 [start, end],两个数组都依 start 非递减排序,且同一数组内没有重叠区间。返回所有同时落在 AB 的区间,结果也要按起点排序。

例如 A = [[1,5],[10,14]]B = [[2,3],[4,12]],交集是 [[2,3],[4,5],[10,12]]。端点相等算有交集;因此 [1,2][2,4] 的交集是 [2,2]

面试官考察点

面试官要看你能否把「两个已排序序列」转成双指针扫描,而不是把所有区间两两比较。强回答会先定义闭区间语义,再用 max(start)min(end) 计算当前交集,最后说明为什么只前进结束较早的那一边。

回答前需要澄清的问题

  1. 区间是闭区间还是半开区间?这会改变端点相等时是否输出。
  2. 每组是否保证已排序且组内不重叠?若不保证,需先排序,或先合并每组区间。
  3. 输入是否可能为空、包含单点区间或 start > end?这决定验证与错误处理。
  4. 是否要求保留重叠长度为零的单点?本题闭区间要求保留。

30 秒回答框架

「我用两个指针 ij 从两组第一个区间开始。当前交集左端是两个起点的较大值,右端是两个终点的较小值;只要左端不大于右端就输出。接着移动终点较小的区间,因为它已不可能再和另一个当前或更后面的区间产生新的交集。每个指针最多走完整个数组,所以时间是 O(m+n),额外空间不含输出为 O(1)。」

分步骤深入解答

第一步:固定区间语义

将每个区间视为 [start,end]。若 left = max(A[i].start, B[j].start)right = min(A[i].end, B[j].end),左端不大于右端就代表存在交集,两端相等是合法单点。

第二步:推导指针移动

A[i].end 小于 B[j].endA[i] 在时间轴上先结束。由于 B[j] 之后的区间起点不会更早,A[i] 不可能再与 B[j+1] 或更后区间重叠,因此只能 i++B[j].end 小于 A[i].end 时对称地 j++

第三步:处理同时结束

若两个终点相等,两边都已经没有剩余范围可和下一个区间重叠,必须同时递增 ij。只递增一边会再次拿已结束的区间比较,可能漏掉下一段或增加无效步骤。

第四步:写出可执行骨架

ts
function intersect(A: number[][], B: number[][]): number[][] {
  const out: number[][] = [];
  let i = 0;
  let j = 0;

  while (i < A.length && j < B.length) {
    const left = Math.max(A[i][0], B[j][0]);
    const right = Math.min(A[i][1], B[j][1]);
    if (left <= right) out.push([left, right]);

    if (A[i][1] < B[j][1]) i++;
    else if (B[j][1] < A[i][1]) j++;
    else { i++; j++; }
  }
  return out;
}

第五步:用不变量说明正确性

每次回圈开始时,ij 指向尚未证明不可能产生交集的最早区间。计算出的 [left,right] 是这两个区间唯一可能的交集;若有交集就输出。移动较早结束的一侧后,所有被跳过的配对都因时间顺序不可能相交,因此不会漏解。

第六步:分析复杂度与输入防御

两个指针只会前进,最多 m+n 次,时间复杂度 O(m+n)。输出数组不计入额外工作空间时是 O(1);若计入输出,空间是 O(k)。若契约不保证排序或有效端点,应先验证或排序,不能直接套用线性解法。

高质量示例回答

我先确认区间是闭区间、两组都已按起点排序且组内不重叠。扫描 A[i]B[j] 时,交集就是起点取大、终点取小;在闭区间下左端不大于右端都要输出。之后我移动终点较小的指针,因为它已经结束,后面的区间起点只会更晚,不可能补回新的交集;两个终点相等则同时移动。这样每个区间只被处理一次,时间 O(m+n)。我会测空数组、没有交集、端点相等、单点区间、完全包含与多段连续交集。

常见错误

  • 错误表现 → 只在左端小于右端时输出 → 失败原因:闭区间的单点交集被漏掉 → 修正方法:先确认契约,左端不大于右端时也要输出。
  • 错误表现 → 每个 A 区间扫过所有 B 区间 → 失败原因:忽略排序,时间退化为 O(mn) → 修正方法:维护两个单调指针。
  • 错误表现 → 永远只递增 i → 失败原因:B[j] 可能先结束,导致重复比较或漏解 → 修正方法:比较两个终点,较小者前进,同值时两者前进。
  • 错误表现 → 输入未排序仍宣称线性 → 失败原因:指针移动的证明失效 → 修正方法:补上排序或每组先合并的前置步骤。

追问及应对

如果区间改成半开区间 [start,end),哪些条件要改?

交集存在的条件改成左端小于右端;[1,2)[2,4) 不产生单点交集。指针移动的终点比较仍可沿用,但要在答案中明确说明端点语义。

如果每组区间没有排序且可能重叠,还能做到 O(m+n) 吗?

不能直接做到。先各自按起点排序,并合并组内重叠区间,再做双指针;成本至少包含排序的 O(m log m+n log n),之后扫描仍是线性。

如果要返回交集长度总和,而不是区间列表呢?

仍用相同扫描;每次有交集就累加 right-left 加上端点是否包含的修正。对闭区间的整数时间轴,需先定义长度是几何长度还是点数,不能直接混用。

如果输入是两个数据流,不能回看已读区间呢?

只要每个数据流仍按起点排序,双指针状态可以保留当前区间与下一次读取位置。输出一段交集后丢弃已结束的一侧;若要处理回溯或乱序,就需要缓冲与不同设计。

公开来源

同类题目

相关面试工具

用 Screenshot 处理算法题

截图题目后,按顺序看约束、解法、代码、边界条件和复杂度。

查看工具