leetcode:632. 最小区间

题目来源

题目描述

在这里插入图片描述

class Solution {
public:
    vector<int> smallestRange(vector<vector<int>>& nums) {

    }
};

题目解析

分析数据

  • 这是一个二维数组,二维数组最大长度为3500;里面的一维数组长度最大50;如果每个数字看一遍,需要 3500 * 50 = 175000 = 10^5。maybe行列对应模型???

在这里插入图片描述

  • 数据范围比较大,行列对应模型pass
    在这里插入图片描述
  • 从下面可以看出,具有单调性,能不能利用这个单调性呢?二分?单调栈?滑动窗口?

在这里插入图片描述

分析题意

  • 要求选出一个区间,在这个区间范围内每一维都至少有一个数字落在这上面,而且要求区间最窄
  • 最窄区间可能不止一个,选开始值最小的

在这里插入图片描述

也就是说最小区间,必须满足如下两点:

  • 长度最窄(首要)
  • 长度相同时起点最小

思考

  • 怎么保证长度最窄呢?
    • 这肯定是在查看的过程中计算得到的
    • 为了使得区间最窄,所以区间的左右端点应该刚好覆盖住数组的某个数字
    • 为了使得区间覆盖住所有数组,所以区间的左右端点应该是某一列中的最大值和最小值{min,max}
  • 那怎么保证起点区间最小呢?
    • 每次将当前区间中最小的元素丢弃,换成其原始数组中的下一个元素
    • 就是说,如果当前我们从每个区间中选取的元素分别是 a1_1,a2_1,a3_1…ak_1 (前面的数字代表来自第几个数组,后面的数字表示该数是该数组的第几个元素),若此时最小的元素是 ai_1,最大元素是 aj_1 ,那么区间就为 [ai_1, aj_1],区间中最小元素是 ai_1,那么我们就将 ai_1 丢弃,将 ai_2 拿出来放进去。
    • 原因:
      • 如果不换ai_1,那么区间的起点就一直是ai_1,而且区间的长度不可能缩小,区间长度取决于起点和终点,而终点是不可能变小的。
      • 因为我们是从小到大进行元素的选取,我们每丢弃一个元素,就要选择它在原始数组中的下一位,而原始数组是升序的,故终点不可能减小。
      • 那么,我们只能通过让起点增大来是区间缩小,即每次丢弃最小的元素,换成它原数组的下一个元素。然后再计算当前所选区间的长度,如果小于之前的区间长度就更新即可。
  • 结束条件:当当前最小元素是其原来整数数组的最后一个元素时,就是结束的时候。因为之后的操作不可能更改起点了,只会让终点变大,即区间变长。

大思路

  • 每个数组的第一个数加入到有序表,取出最大值和最小值,就可以找到一个区间。这个区间一定每个数组都覆盖了
  • 然后删除最小值,把这个最小值来自数字的下一个值加入到有序表中,重新取出最大值和最小值,构成了一个新的区间,跟之前的区间比较是否更优
  • 当某一个数组耗尽了,不用再继续了,找到了最窄区间

实例一

在这里插入图片描述

有序表

java中的有序表可以用treeSet,它可以有序表可以非常方便的找出所有数组的最大值和最小值

  • C++的有序表可以用:
    • set:key不会重复
    • multiset:key可以重复,但是erase(key),其所有重复的key都会删除
    • multimap:
    • priority_queue:可以保证有序而且重复,但是单独的一个priority_queue只能查最大值或者最小值,不能同时查

怎么办?

  • 用两个变量为维持即可
class Solution {
    // 最小堆
    struct NumGroup{
        int num; //数值
        int grp; //组号
        int idx;
        NumGroup(int num, int grp, int idx){
            this->num = num;
            this->grp = grp;
            this->idx = idx;
        }

        // 方法二定义pq 需要重载 operator>
        bool operator> (const NumGroup &other) const {
            return this->num > other.num;
        }
    };

public:
    vector<int> smallestRange(vector<vector<int>>& nums) {
        //因为每次都要找最小元素,所以维护一个最小堆比较合适
        std::priority_queue<NumGroup, std::vector<NumGroup>, std::greater<NumGroup>> minHeap;


        // 最小区间的边界范围
        int left = 0, right = INT16_MAX;

        // 当前列表选择的数字里的最小值和最大值
        int  winMin = INT16_MAX, winMax = INT16_MIN;
        for (int i = 0; i < nums.size(); ++i) {
            winMax = std::max(winMax, nums[i][0]);
            minHeap.emplace(NumGroup(nums[i][0], i, 0));
        }



        while (true){
            // 弹出来的就是目前的最小值
            int grp = minHeap.top().grp;
            int idx = minHeap.top().idx;
            int num = minHeap.top().num;
            minHeap.pop();


            winMin = num;
            winMax = winMax;



            //长度变小
            if(winMax - winMin < right - left){
                right = winMax;
                left = winMin;
            }

            // 提前结束的判断
            if(idx == nums[grp].size() - 1){
                break;
            }

            idx++;
            num =  nums[grp][idx];
            minHeap.emplace(NumGroup(num, grp, idx));
            winMax = std::max(winMax, num);
        }
        return {left, right};
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值