力扣:632. 最小区间(贪心)

你有 k非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。

我们定义如果 b-a < d-c 或者在 b-a == d-ca < c,则区间 [a,b][c,d] 小。

示例 1:

复制代码
输入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
输出:[20,24]
解释: 
列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。
列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。
列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。

示例 2:

复制代码
输入:nums = [[1,2,3],[1,2,3],[1,2,3]]
输出:[1,1]

提示:

  • nums.length == k
  • 1 <= k <= 3500
  • 1 <= nums[i].length <= 50
  • -105 <= nums[i][j] <= 105
  • nums[i] 按非递减顺序排列)

题目要求,给出若干个非递减序列,要算出一个区间[l,r],使得每个序列至少有一个数在区间内。

思路:

既然题目要求每个序列中至少有一个数在区间内,那么索性先选取各个序列中最小的数也就是各个序列的首元素构成一个集合,此时,集合内的数就会生成一个区间 [l1,r1],l1就是所有序列首元素的最小值,r1就是首元素的最大值。这时候的区间一定满足 "每个序列至少有一个数在区间内"的条件,但未必是最小的,之后就要压缩区间。

在压缩区间之前必须明确,在[l1,r1]与初始集合的基础上进行压缩,集合内必须保证含有每个序列中的数。而怎么压缩区间就体现了贪心的思想,每次压缩都从集合中弹出最小的数,之后,为了满足"每个序列至少有一个数在区间内"的条件,需要在其所属序列中选择下一个数加入集合,获取新集合的区间对比更新所求区间。

例如:当前从集合中弹出的元素是nums[i][j],那么就需要将nums[i][j+1]加入集合,假设弹出前集合的区间为[l1,r1],l1为集合中最小元素,r1为集合中最大元素,[l1,r1]为当前压缩到的最小区间,即ans区间;加入元素后的新集合对应的新区间为[l2,r2],此时就要将ans与新区间进行对比,如果新区间小于ans,就更新ans为新区间,否则ans不动。

解释:为什么每次弹出集合中最小的数,加入其序列中的下一个数?

答:因为题目保证所有序列都为非递减序列,则每次弹出集合中最小的数(也就是l1),并加入其原序列的下一个数x,那么x一定大于l1。如果,此时x为新集合中最小的数,那么新集合的新区间为[x,r1]的长度一定小于原区间长度,又因为x是从l1的序列中加入集合的,说明弹出、加入的过程不会影响到集合中其他序列的数,也就是一定不会破坏"每个序列至少有一个数在区间内"的条件,所以[x,r1]一定是当前满足题意的最小区间,随之更新ans,从而达到压缩区间的目的;如果此时x不为新集合中最小的数,最小的数为l2,x可能大于r1,也可能在[l2,r1]之间,此时就要对比新集合的新区间与ans的大小,判断是否更新ans同样可以压缩区间。即使某个序列中有多个数在区间也无所谓,只要满足"每个序列至少有一个数在区间内"的条件即可。

至此,不断弹出,加入元素,保证集合内元素个数不变,不断在贪心的基础上压缩,直到集合不可再压缩,返回ans区间。

AC代码:

C++:

cpp 复制代码
//力扣:632. 最小区间
class Solution {
public:
    typedef struct node{
        int data;//当前元素数据
        int i;//原序列位置
        int idx;//所在原序列下标
        bool operator < (const node& a)const {
            if (data == a.data) {
                return i < a.i;
            }
            return data < a.data;
        }
    }Node;
    vector<int> smallestRange(vector<vector<int>>& nums) {
        vector<int> ans;
        if (nums.empty()) {
            return ans;
        }
        set<Node> s;
        for (int i = 0; i < nums.size(); i++) {
            //初始化集合,将每个序列最小数加入集合
            if (nums[i].empty()) {
                continue;
            }
            Node t = { nums[i][0],i,0 };
            s.insert(t);
        }
        if (s.empty()) {
            return ans;
        }
        int l = (*s.begin()).data;
        int r = (*s.rbegin()).data;

        //压缩区间
        while (1) {
            auto it = s.begin();
            int i = (*it).i;
            int idx = (*it).idx;
            if (idx == nums[i].size() - 1) {
                //最小元素弹出后无法再加入元素
                break;//跳出循环,压缩结束
            }
            else {
                s.erase(s.begin());
                Node t = { nums[i][idx + 1],i,idx + 1 };
                s.insert(t);
                if (((*s.rbegin()).data - (*s.begin()).data) < (r - l)) {
                    //遇到更小的区间,更新
                    r = (*s.rbegin()).data;
                    l = (*s.begin()).data;
                }
                else if (((*s.rbegin()).data - (*s.begin()).data) == (r - l)) {
                    //同样长度的区间选择字典序更小的,更新
                    if ((*s.begin()).data < l) {
                        r = (*s.rbegin()).data;
                        l = (*s.begin()).data;
                    }
                }
            }
        }

        ans.push_back(l);
        ans.push_back(r);
        return ans;
    }
};

java:

java 复制代码
class Solution {
        public class Node implements Comparable <Node> {
            int data;
            int i;
            int idx;

            public Node(int data, int i, int idx) {
                this.data = data;
                this.i = i;
                this.idx = idx;
            }
            public int compareTo(Node a){
                if(this.data==a.data){
                    return this.i-a.i;
                }
                return this.data-a.data;
            }
        }
        public int[] smallestRange(List<List<Integer>> nums) {
            int ans[]=new int[2];
            if(nums.size()==0){
                return ans;
            }
            TreeSet<Node> s=new TreeSet<>();
            for (int i = 0; i < nums.size(); i++) {
                if(nums.get(i).size()==0){
                    continue;
                }
                Node t=new Node(nums.get(i).get(0),i,0);
                s.add(t);
            }
            if(s.size()==0){
                return ans;
            }
            int l=s.first().data;
            int r=s.last().data;
            while(true){
                Node it=s.first();
                int i=it.i;
                int idx=it.idx;
                if(idx==nums.get(i).size()-1){
                    break;
                }
                else{
                    s.remove(s.first());
                    Node t=new Node(nums.get(i).get(idx+1),i,idx+1);
                    s.add(t);
                    int len=s.last().data-s.first().data+1;
                    if(len<(r-l+1)){
                        l=s.first().data;
                        r=s.last().data;
                    }
                    else if(len==(r-l+1)){
                        if(l>s.getFirst().data){
                            l=s.first().data;
                            r=s.last().data;
                        }
                    }
                }
            }
            ans[0]=l;
            ans[1]=r;
            return ans;
        }
    }
相关推荐
日月云棠2 小时前
各版本JDK对比:JDK 25 特性详解
java
爱理财的程序媛3 小时前
openclaw 盯盘实践
算法
用户8307196840823 小时前
Spring Boot 项目中日期处理的最佳实践
java·spring boot
JavaGuide3 小时前
Claude Opus 4.6 真的用不起了!我换成了国产 M2.5,实测真香!!
java·spring·ai·claude code
IT探险家4 小时前
Java 基本数据类型:8 种原始类型 + 数组 + 6 个新手必踩的坑
java
花花无缺4 小时前
搞懂new 关键字(构造函数)和 .builder() 模式(建造者模式)创建对象
java
用户908324602734 小时前
Spring Boot + MyBatis-Plus 多租户实战:从数据隔离到权限控制的完整方案
java·后端
桦说编程5 小时前
实战分析 ConcurrentHashMap.computeIfAbsent 的锁冲突问题
java·后端·性能优化
MobotStone6 小时前
Google发布Nano Banana 2:更快更便宜,图片生成能力全面升级
算法
程序员清风9 小时前
用了三年AI,我总结出高效使用AI的3个习惯!
java·后端·面试