【C/C++】757. 设置交集大小至少为2

227 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情


题目链接:757. 设置交集大小至少为2

题目描述

一个整数区间 [a, b]  ( a < b ) 代表着从 a 到 b 的所有连续整数,包括 a 和 b

给你一组整数区间 intervals,请找到一个最小的集合 S,使得 S 里的元素与区间 intervals 中的每一个整数区间都至少有 2 个元素相交。

输出这个最小集合 S 的大小。

提示:

  • intervals 的长度范围为 [1, 3000]
  • intervals[i] 长度为 2,分别代表左、右边界。
  • intervals[i][j] 的值是 [0, 10^8] 范围内的整数。

示例 1:

输入: intervals = [[1, 3], [1, 4], [2, 5], [3, 5]]
输出: 3
解释:
考虑集合 S = {2, 3, 4}. S与intervals中的四个区间都有至少2个相交的元素。
且这是S最小的情况,故我们输出3

示例 2:

输入: intervals = [[1, 2], [2, 3], [2, 4], [4, 5]]
输出: 5
解释:
最小的集合S = {1, 2, 3, 4, 5}.

整理题意

题目给定一个整数区间数组 intervals,一个整数区间 [a, b]  ( a < b ) 代表着从 a 到 b 的所有连续整数,包括 a 和 b

让我们返回一个最小的整数集合 S,这个集合与给定的所有区间都至少有两个元素相交。

需要注意这里返回的是整数集合 S 的大小,并不是连续区间的大小,题目示例具有误导性。

解题思路分析

首先对所有区间进行 排序 处理,按照左区间升序并且在左区间相同的情况下右区间降序排序,此时从后往前遍历所有区间,对于最后一个区间来说,我们思考我们选择该区间中哪两个元素是最优的(最优的元素应该尽可能的把之前的区间尽可能的覆盖)。那么我们选择该区间的开头元素一定是最优的,因为对于前面的某一个区间来说,能够最先相交的元素就是开头的两个元素。

那么按照上述 贪心思路 可得:排序后从后往前进行遍历,判断当前区间与已经选取的元素集合是否存在交集:

  • 如果交集个数大于两个就直接跳过;
  • 如果交集个数为一个,那么再选取当前区间最左边的一个元素即可;
  • 如果没有交集,那么选取当前区间最左边的两个元素即可。

为什么需要按照左区间升序并且在左区间相同的情况下右区间降序排序?

这是因为左区间相同,右区间降序,会使得在左区间相同的情况下让区间范围最小的在最右边(先处理区间范围较小的),对于区间来说当我们选取范围最小的区间最左边的两个元素时,其他左区间同的区间(范围更大)也必定是包含这两个元素的,因为小区间被大区间包含,反过来不一定。在左区间相同的情况下,我们取最小区间的两个元素就可以满足所有左区间相同的区间都包含这两个元素。

具体实现

  1. 对区间元素进行排序,按照左区间升序,在左区间相同的情况下右区间降序进行排序。
  2. 我们贪心的取最后一个区间最左边的两个元素 interval[n-1][0]interval[n-1][0] + 1 做为开始的两个集合元素。
  3. 从后往前遍历所有区间元素,不断维护已经选取的元素集合中最小的两个元素。
  4. 判断当前区间与这两个元素的相交情况:
    • 如果包含这两个元素就直接跳过;
    • 如果只包含一个,那么再选取当前区间最左边的一个元素即可;
    • 如果没有交集,那么选取当前区间最左边的两个元素即可。
  5. 最后返回集合大小即可。

因为只需要记录集合中最小的两个元素,所以可以使用两个变量进行记录即可,集合的大小也可以通过一个变量来维护。

复杂度分析

  • 时间复杂度:O(nlogn+nm)O(n \log n + nm),其中 n 为给定区间集合 intervals 的大小,m 为设置交集大小,本题为 2
  • 空间复杂度:O(nm)O(nm),其中 n 为给定区间集合 intervals 的大小,m 为设置交集的大小,本题为 2。主要开销为存储每一个区间与交集集合的相交的元素的开销。

代码实现

class Solution {
public:
    int intersectionSizeTwo(vector<vector<int>>& intervals) {
        //按照左区间升序,右区间降序排序
        sort(intervals.begin(), intervals.end(), [](const vector<int> &a, const vector<int> &b)->bool{
            if(a[0] == b[0]) return a[1] > b[1];
            return a[0] < b[0];
        });
        int n = intervals.size();
        int cur = intervals[n - 1][0], nxt = intervals[n - 1][0] + 1;
        int res = 2;
        //倒叙遍历
        for(int i = n - 2; i >= 0; i--){
            if(intervals[i][1] >= cur && intervals[i][1] < nxt){
                nxt = cur;
                cur = intervals[i][0];
                res++;
            }
            else if(intervals[i][1] < cur){
                cur = intervals[i][0];
                nxt = intervals[i][0] + 1;
                res += 2;
            }
        }
        return res;
    }
};

总结

  • 需要注意在 sort 自定义排序中的自定义 lambda 函数 [](const vector<int> &a, const vector<int> &b)->bool{...}:由于未使用到函数外的参数,所以不用写成 [&],这里将 a 和 b 作为常量引用,即避免了额外使用空间进行拷贝,又避免了在函数中修改该参数,函数的返回值类型 ->bool 可以不写,剩下的 {...} 就是函数体本身了。
  • 该题的核心思想为 排序贪心,巧妙的排序使得该题可以贪心选取元素。
  • 测试结果:

757.png

结束语

生活不要安排得太满,人生不要设计得太挤。不管做人还是做事,都学会给自己留点空间。不冒进也不颓废,不紧绷也不放纵,给人生留点余地,才能迎接更多出其不意的惊喜。新的一天,加油!