题目描述

✅ 986. 区间列表的交集

image-20260928220233434

image-20260928220233436

题意分析

两个列表各自按区间起点递增排列,同一列表内的区间互不相交。要求返回同时属于两个列表的所有交集区间。区间是闭区间,所以只共享一个端点时,也要返回这个单点区间。

原图勘误:示例 1 的正确输出为 [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]],六项分别来自 A[0] 与 B[0]、A[1] 与 B[0]、A[1] 与 B[1]、A[2] 与 B[2]、A[3] 与 B[2]、A[3] 与 B[3]。末尾两个单点不能合成 [24,25],因为两个端点之间的数不属于列表 B。示例 2 的正确输出为 [[2,3],[6,8]],第二段左端点应为 6。

解法:双指针扫描两个有序区间列表

核心思路

[!blue]

用 i、j 指向两个列表当前尚未处理完的区间。一个数要同时落在两个区间内,必须不小于两个左端点,也不大于两个右端点。因此交集边界为 left = max(左端点)、right = min(右端点);left <= right 时交集非空,否则这两个区间没有交集。

计算完当前交集后,应淘汰结束更早的区间。设第一个区间右端点为 a,第二个为 b,且 a < b。第二个列表内部有序且不重叠,后续区间的起点一定大于 b,也就大于 a,因此第一个区间不可能再与它们相交,可以安全执行 i++。第二个区间还可能与第一个列表的后续区间相交,所以暂时保留。b < a 时同理移动 j。

若 a == b,两者都不可能再与对方的后续区间相交,移动任意一侧都不会漏解。代码选择移动 j;若循环继续,保留的第一个区间会在下一次比较中被淘汰。无论当前是否产生交集,都按这个右端点规则推进。

每次跳过的区间都已经找完它可能产生的交集,因此不会漏掉答案。任一列表耗尽时,另一列表剩余区间也无法与已经处理完的区间再产生交集,可以直接结束;输出随指针从左到右产生,无需额外排序。

解题步骤

  1. 初始化 i = 0、j = 0 和空结果列表。
  2. 当两侧指针都未越界时,计算当前两个区间的 left、right。
  3. 若 left <= right,将 [left, right] 加入结果;相等时也要加入。
  4. 若第一个区间右端点更小,执行 i++;否则执行 j++。
  5. 一侧扫描完就返回结果。一开始有空列表时,循环不会进入,结果自然为空。

代码实现

class Solution {
    public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {
        List<int[]> result = new ArrayList<>();
        int i = 0;
        int j = 0;

        while (i < firstList.length && j < secondList.length) {
            int left = Math.max(firstList[i][0], secondList[j][0]);
            int right = Math.min(firstList[i][1], secondList[j][1]);

            // 闭区间端点相等也产生一个单点交集。
            if (left <= right) {
                result.add(new int[] {
                    left,
                    right
                });
            }

            // 结束更早的一侧不会再与另一列表的后续区间相交。
            if (firstList[i][1] < secondList[j][1]) {
                i++;
            } else {
                j++;
            }
        }

        return result.toArray(new int[0][]);
    }
}
func intervalIntersection(firstList [][]int, secondList [][]int) [][]int {
    result := make([][]int, 0)
    for i, j := 0, 0; i < len(firstList) && j < len(secondList); {
        left := maxInt(firstList[i][0], secondList[j][0])
        right := minInt(firstList[i][1], secondList[j][1])
        // 闭区间端点相等也产生一个单点交集。
        if left <= right {
            result = append(result, []int{
                left,
                right,
            })
        }

        // 结束更早的一侧不会再与另一列表的后续区间相交。
        if firstList[i][1] < secondList[j][1] {
            i++
        } else {
            j++
        }
    }
    return result
}

func maxInt(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func minInt(a, b int) int {
    if a < b {
        return a
    }
    return b
}

复杂度分析

  • 时间复杂度:$O(m+n)$,其中 $m$、$n$ 是两个列表的长度。每轮至少推进一个指针,每个区间最多被指针越过一次。
  • 空间复杂度:若返回 $r$ 个交集区间,结果占 $O(r+1)$ 空间。除结果外,Java 转数组前还需要 $O(r+1)$ 的临时列表引用,Go 只需要 $O(1)$ 的辅助空间。

关键点总结

[!green]

  • 交集统一取较大的左端点、较小的右端点,不必区分包含、交叉或分离。
  • 淘汰右端点更小的区间,依据是另一列表后续区间不可能再覆盖它。
  • “有序且各自内部不相交”保证安全推进,也是线性扫描成立的前提。

易错点总结

[!yellow]

  • 非空条件必须是 left <= right,写成严格小于会漏掉单点交集。
  • 不能按左端点决定移动哪一侧,也不能每次都同时移动两侧,否则可能跳过仍有贡献的长区间。
  • 即使当前没有交集,也要推进右端点更小的一侧,不能停在原地。
  • 一侧结束后不能直接追加另一侧剩余区间;题目求的是交集,剩余区间不自动属于结果。

相似题目

题目 难度 关联与区别
56. 合并区间 中等 原题计算区间并集,本题对两个有序来源求交集,区间右端较小的一侧可以先推进。
57. 插入区间 中等 同样按有序区间的边界关系分支,本题不修改输入,只输出重叠部分。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/71305127
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!