题目描述

✅ 757. 设置交集大小至少为2

image-20260929104710123

image-20260929104710281

题意分析

选择尽可能少的整数,使每个给定闭区间都至少包含其中两个不同整数。

解法:排序 + 贪心维护最后两个点

核心思路

[!blue]

按右端点升序处理区间,同右端时按左端点降序,让更紧的区间先处理。对当前 [l, r],之前选入的所有点都不超过 r,所以只需记录最大的两个点 s < e,就能判断当前区间已覆盖了几个点。

  • 若 l <= s,最大的两个点都在区间内,已经满足要求,不补点。
  • 若 s < l <= e,只有最大点 e 落在区间内,至少还需一个点,选择 r,再令 s = e、e = r。
  • 若 e < l,所有旧点都在区间左侧,至少还需两个点,选择 r-1、r,并将它们作为新的最大两点。

缺几个点就至少要增加几个点,剩下只需证明新增点应尽量靠右。固定已经选中的点,将某个最优补全方案为当前区间提供的缺失点,换到当前区间最右侧的可选位置:此前区间已由固定点满足;后续区间的右端不小于 r,新增点向右移动后仍不超过它们的右端,也不会更容易落到它们左端之外。因此这种替换不会增加点数或破坏已有覆盖,贪心选择能够保留一个最优解。

同右端的左端降序保证了只缺一个点时 e < r,从而补入的 r 是新点。若 e 已经等于 r,它来自此前同右端、左端更大的区间;满足那个更紧区间的两个点也都在当前区间内,应当直接进入不补点的分支,不会再次添加同一个 r。

题目保证 l < r,所以 r-1 与 r 是当前区间内两个不同整数。左端点均非负,初始的 s = e = -1 只表示尚未选择点,第一个区间会正常补入两个点。

解题步骤

  1. 按右端升序、左端降序排序。
  2. 维护已选点中最大的两个点和答案数量。
  3. 已有至少两个点落入区间时跳过。
  4. 只覆盖一个点就补一个,否则补右端相邻的两个点。

代码实现

class Solution {
    public int intersectionSizeTwo(int[][] intervals) {
        // 右端升序,同右端先处理左端更大的紧区间。
        Arrays.sort(intervals, (a, b) -> a[1] != b[1] ? a[1] - b[1] : b[0] - a[0]);

        int answer = 0;
        int s = -1;
        int e = -1;

        for (int[] it : intervals) {
            int l = it[0];
            int r = it[1];

            // 最大的两个已选点都落在区间中,无需再补点。
            if (l <= s) {
                continue;
            }

            // 只有一个点被覆盖时补右端,否则补右端附近两个点。
            if (l <= e) {
                answer += 1;
                s = e;
                e = r;
            } else {
                answer += 2;
                s = r - 1;
                e = r;
            }
        }

        return answer;
    }
}
import "sort"

func intersectionSizeTwo(intervals [][]int) int {
    // 右端升序,同右端先处理左端更大的紧区间。
    sort.Slice(intervals, func(i, j int) bool {
        if intervals[i][1] != intervals[j][1] {
            return intervals[i][1] < intervals[j][1]
        }
        return intervals[i][0] > intervals[j][0]
    })

    answer := 0
    s, e := -1, -1

    for _, interval := range intervals {
        l, r := interval[0], interval[1]

        // 最大的两个已选点都落在区间中,无需再补点。
        if l <= s {
            continue
        }
        // 只有一个点被覆盖时补右端,否则补右端附近两个点。
        if l <= e {
            answer++
            s = e
            e = r
        } else {
            answer += 2
            s = r - 1
            e = r
        }
    }

    return answer
}

复杂度分析

  • 时间复杂度:$O(n\log(n+1))$,n 为区间数,排序后线性扫描。
  • 空间复杂度:扫描辅助空间 $O(1)$;Java 对象数组排序工作区为 $O(n)$,Go 排序栈为 $O(\log(n+1))$。

关键点总结

[!green]

  • 两个点必须不同,同右端的排序规则避免重复补入已有右端。
  • 不必保存全部选点,最大的两个足以判断当前覆盖情况。
  • 区间是闭区间,端点本身可以被选取。

易错点总结

[!yellow]

  • 缺两个点时选左端附近:会减少对后续区间的复用。
  • 同右端不先处理更紧区间:补点分支可能重复选到同一个右端。
  • 只记录最大的一个点:无法区分当前覆盖一个还是至少两个。
  • 使用严格大于判断点在左端内:遗漏端点相等的合法覆盖。

相似题目

题目 难度 关联与区别
452. 用最少数量的箭引爆气球 中等 原题每个区间至少命中一个点,本题至少两个点,可继续按右端点贪心并维护最近选择。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2022/51516451
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!