【Leetcode 每日一题】632. 最小区间

news2024/11/27 1:32:33

问题背景

你有 k k k非递减排列 的整数列表。找到一个 最小 区间,使得 k k k 个列表中的每个列表至少有一个数包含在其中。
我们定义如果 b − a < d − c b - a < d - c ba<dc 或者在 b − a = = d − c b - a == d - c ba==dc a < c a < c a<c,则区间 [ a , b ] [a,b] [a,b] [ c , d ] [c,d] [c,d] 小。
数据约束

  • n u m s . l e n g t h = = k nums.length == k nums.length==k
  • 1 ≤ k ≤ 3500 1 \le k \le 3500 1k3500
  • 1 ≤ n u m s [ i ] . l e n g t h ≤ 50 1 \le nums[i].length \le 50 1nums[i].length50
  • − 1 0 5 ≤ n u m s [ i ] [ j ] ≤ 1 0 5 -10 ^ 5 \le nums[i][j] \le 10 ^ 5 105nums[i][j]105
  • n u m s [ i ] nums[i] nums[i] 按非递减顺序排列

解题过程

解决这个问题的关键在于,想明白答案是从每个区间的左端获得的。由于给定的列表中的子列表元素有序,所以对于某一个状态的问题列表而言,结果的左端点就是所有列表中第一个元素的最小值,结果的右端点就是所有列表中第一个元素的最大值。
不过由于题中区间的大小是由区间长度来决定的,所以有可能出现除了第一个元素以外的其他元素组成更小的区间的情况。因此还需要考虑使用第一个元素以外的元素时,能否构成更小的区间。也就是说,每一轮循环中,要做的是根据当前所有列表中最小元素确定右端点。因此,我们需要一个能够根据其中元素的情况来维护最小值的数据结构,选用最小堆。
堆中的元素可以是一个三元组,除了最小的元素,额外保存它所在的列表序号,以及在列表中的下标,这样可以方便地在题中所给列表 n u m s nums nums 中进行定位。更新状态时,不必移除元素,改变堆顶三元组中的左端点即可。
这样做的时间复杂度是 O ( L l o g k ) O(Llogk) O(Llogk),其中 L L L 是所有列表的长度之和,其中 k k k 是题中所给数组 n u m s nums nums 的长度。循环 L L L 次,每次循环需要 l o g k logk logk 量级的时间操作堆。空间复杂度是 O ( k ) O(k) O(k),堆需要一定的空间来保存元素。

另一种思路是,记录每个元素所在的列表编号之后,将所有列表中的数字放在一起排序。
这时候问题就转变为,在有序序列中找到最短的子数组,使得其中包含每个列表中至少一个元素。子数组中包含的每个列表中的元素数量随着子数组长度增长而增加,可以滑窗。
这种思路的时间复杂度是 O ( L l o g L ) O(LlogL) O(LlogL) 或者 O ( L ) O(L) O(L),其中 L L L 是所有 n u m s [ i ] nums[i] nums[i] 的长度之和。考虑到瓶颈在排序上,将默认排序算法 T i m S o r t TimSort TimSort 替换成昨天刚刚整理过的计数排序 C o u n t i n g S o r t CountingSort CountingSort,可以有效提升性能。空间复杂度是 O ( L ) O(L) O(L),需要额外空间来保存包含所有元素的数组。

具体实现

维护最小堆

class Solution {
    public int[] smallestRange(List<List<Integer>> nums) {
        // Java 中可以定义优先队列,自定义排序方式来作为堆
        PriorityQueue<int[]> priorityQueue = new PriorityQueue<>((i1, i2) -> i1[0] - i2[0]);
        int right = Integer.MIN_VALUE;
        for(int i = 0; i < nums.size(); i++) {
            // 原列表中所有的第一个元素入堆
            int cur = nums.get(i).get(0);
            // 同时记录该元素在原列表中的坐标
            priorityQueue.offer(new int[]{cur, i, 0});
            // 右端点是所有第一个元素中的最大值
            right = Math.max(right, cur);
        }

        int resL = priorityQueue.peek()[0];
        int resR = right;
        // 堆顶三元组中下标为 2 的元素记录的是当前元素的位置,先自增表示下一个元素的位置
        // 循环结束的条件是出现某个列表里没有下一个元素,无法构成合法区间
        while(++priorityQueue.peek()[2] < nums.get(priorityQueue.peek()[1]).size()) {
            // 获取并更新堆顶三元组
            int[] top = priorityQueue.poll();
            top[0] = nums.get(top[1]).get(top[2]); // 经过自增,这里取得的是下一个元素
            // 取出来之后更新右端点
            right = Math.max(right, top[0]);
            // 复用三元组,将修改了值之后的新三元组入堆
            priorityQueue.offer(top);
            // 从调整之后的合法堆中获取当前第一个元素的最小值
            int left = priorityQueue.peek()[0];
            if(right - left < resR - resL) {
                resL = left;
                resR = right;
            }
        }
        return new int[] {resL, resR};
    }
}

排序后滑窗

class Solution {
    public int[] smallestRange(List<List<Integer>> nums) {
        // 计算保存所有元素需要的数组长度
        int length = 0;
        for(List<Integer> list : nums) {
            length += list.size();
        }

        // 将列表中所有元素记录到新数组中
        int[][] pairs = new int[length][2];
        int index = 0;
        for(int i = 0; i < nums.size(); i++) {
            for(int num : nums.get(i)) {
                pairs[index][0] = num;
                pairs[index++][1] = i;
            }
        }
        
        // 使用 Java 默认的 排序算法 TimSort
        // Arrays.sort(pairs, (a, b) -> a[0] - b[0]);
        // 自己实现二维计数排序 CountingSort2d
        countingSort2d(pairs);

        int resL = pairs[0][0];
        int resR = pairs[length - 1][0];
        int diff = nums.size(); // diff 表示没有元素被包含的列表数量
        int[] count = new int[diff]; // 统计每个列表中被包含的元素数量
        int leftIndex = 0; // 记录当前左端点的所在的列表编号
        for(int[] pair : pairs) {
            int right = pair[0];
            int curIndex = pair[1];
            // 当前列表有元素被包含,diff 减少
            if(count[curIndex]++ == 0) {
                diff--;
            }
            while(diff == 0) {
                int left = pairs[leftIndex][0]; // 取出当前左端点
                // 满足条件的情况下更新答案
                if(right - left < resR - resL) {
                    resL = left;
                    resR = right;
                }
                // 更新左端点所在的列表编号
                curIndex = pairs[leftIndex][1];
                // 如果当前列表中没有元素被包含,diff 增加
                if(--count[curIndex] == 0) {
                    diff++;
                }
                leftIndex++; // 移动左端点
            }
        }
        return new int[] {resL, resR};
    }

    private void countingSort2d(int[][] pairs) {
        // 查找数组中的最小最大值,确定映射范围
        int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
        for(int[] pair : pairs) {
            min = Math.min(min, pair[0]);
            max = Math.max(max, pair[0]);
        }
        int range = max - min + 1;
        int[] count = new int[range];
        // 统计每个元素出现的次数
        for(int[] pair : pairs) {
            count[pair[0] - min]++;
        }
        // 改造成前缀分区的形式,方便快速地确定还原后的元素位置
        for(int i = 1; i < count.length; i++) {
            count[i] += count[i - 1];
        }
        int length = pairs.length;
        // 还原数组元素
        int[][] res = new int[length][2];
        for(int i = length - 1; i >= 0; i--) {
            int cur = pairs[i][0];
            res[--count[cur - min]] = pairs[i];
        }
        // 把结果回写到原数组中
        System.arraycopy(res, 0, pairs, 0, length);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2248130.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot(9)-Dubbo+Zookeeper

目录 一、了解分布式系统 二、RPC 三、Dubbo 四、SpringBootDubboZookeeper 4.1 框架搭建 4.2 实现RPC 一、了解分布式系统 分布式系统&#xff1a;由一组通过网络进行通信&#xff0c;为了完成共同的任务而协调工作的计算机节点组成的系统 二、RPC RPC&#xff1a;远程…

五种创建k8s的configMap的方式及configmap使用

configmap介绍 Kubernetes 提供了 ConfigMap 来管理应用配置数据&#xff0c;将配置信息从容器镜像中解耦&#xff0c;使应用更灵活、可移植。 1、基于一个目录来创建ConfigMap ​ 你可以使用 kubectl create configmap 基于同一目录中的多个文件创建 ConfigMap。 当你基于目…

(原创)Android Studio新老界面UI切换及老版本下载地址

前言 这两天下载了一个新版的Android Studio&#xff0c;发现整个界面都发生了很大改动&#xff1a; 新的界面的一些设置可参考一些博客&#xff1a; Android Studio新版UI常用设置 但是对于一些急着开发的小伙伴来说&#xff0c;没有时间去适应&#xff0c;那么怎么办呢&am…

贵州茅台[600519]行情数据接口

贵州茅台&#xff1a;实时行情 Restful API # 测试接口&#xff1a;可以复制到浏览器打开 https://tsanghi.com/api/fin/stock/XSHG/realtime?tokendemo&ticker600519获取股票实时行情&#xff08;开、高、低、收、量&#xff09;。 请求方式&#xff1a;GET。 Python示例…

MacOS系统上Jmeter 录制脚本遇到的证书坑位

一、JMeter介绍与安装 1&#xff0c;下载及安装 jmeter官网地址 二、录制百度链接https请求时&#xff0c;需要导入jmeter相关证书到macos系统的更目录中. 导入方式&#xff0c;直接拖入mac的系统中&#xff0c;始终新人就可以&#xff1b; 三、jmeter 创建相关的录制组件…

【ArcGISPro】Sentinel-2数据处理

错误 默认拉进去只组织了4个波段,但是实际有12个波段 解决方案 数据下载 Sentinel-2 数据下载-CSDN博客 数据处理 数据查看 创建镶嵌数据集 在数据管理工具箱中找到创建镶嵌数据集

音视频处理PCM相关概念:帧(Frame)、周期(Period Size)、量化、 声道数(Channels)、采样位数(Sample Bits)、采样频率

文章目录 引言I PCM相关图表原始模拟音频数据:模拟信息按照固定频率进行采样对采样后的数据选择合适精度进行量化PCM数据流II PCM相关概念采样频率:单位时间内对模拟信号的采样次数采样位数(Sample Bits)声道数(Channels)音频数据大小计算量化编码III 其他相关参数帧(Fra…

小米note pro一代(leo)线刷、twrp、magisk、TODO: android源码编译

本文主要说android5 整体思路 android 5.1 twrp magisk Zygisk(Riru) Dreamland(xposed) Riru不支持android5.1, 因此只能选择Zygisk : 如果你正在使用 Android 5&#xff0c;你必须使用 Zygisk 因为 Riru 并不支持 Android 5. 基于magisk之上的xposed 其中提到的 作者…

React表单联动

Ant Design 1、dependencies Form.Item 可以通过 dependencies 属性&#xff0c;设置关联字段。当关联字段的值发生变化时&#xff0c;会触发校验与更新。 一种常见的场景&#xff1a;注册用户表单的“密码”与“确认密码”字段。“确认密码”校验依赖于“密码”字段&#x…

【AIGC】如何准确引导ChatGPT,实现精细化GPTs指令生成

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AIGC | 提示词Prompt应用实例 文章目录 &#x1f4af;前言&#x1f4af;准确引导ChatGPT创建爆款小红书文案GPTs指令案例&#x1f4af; 高效开发GPTs应用的核心原则明确应用场景和目标受众构建多样化风格模板提问与引…

Easyexcel(6-单元格合并)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09;Easyexcel&#xff08;4-模板文件&#xff09;Easyexcel&#xff08;5-自定义列宽&#xff09;Easyexcel&#xff08;6-单…

从零开始打造个人博客:我的网页设计之旅

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 ✨特色专栏&#xff1a…

数据库-MySQL-Mybatis源码解析-设计模式角度

文章目录 前言一、工厂模式二、单例模式三、建造者模式四、模板模式五、代理模式六、装饰器模式七、总结 前言 Mybatis是一个比较主流的ORM框架&#xff0c;所以在日常工作中接触得很多。能写出这种框架的作者肯定有其独特之处。阅读优秀框架的源码&#xff0c;如果能看懂些巧…

【K8S问题系列 |18 】如何解决 imagePullSecrets配置正确,但docker pull仍然失败问题

如果 imagePullSecrets 配置正确&#xff0c;但在执行 docker pull 命令时仍然失败&#xff0c;可能存在以下几种原因。以下是详细的排查步骤和解决方案。 1. 检查 Docker 登录凭证 确保你使用的是与 imagePullSecrets 中相同的凭证进行 Docker 登录&#xff1a; 1.1 直接登录…

[工具分享] 根据Excel数据根据Word文档模板,批量创建生成Word文档并重命名,方便快速查找打印

前几天交楼的小姐姐要多份Word文档合同打印给客户&#xff0c;那么100份就需要修改100次 上面好多都是模板的制式文件&#xff0c;里面的部分数据都是要根据实际值来变动的&#xff0c; 那么有没有快速的方法来操作呢&#xff0c;还是只能一个个手动的改&#xff0c;又容易出…

嵌入式的C/C++:深入理解 static、const 与 volatile 的用法与特点

目录 一、static 1、static 修饰局部变量 2、 static 修饰全局变量 3、static 修饰函数 4、static 修饰类成员 5、小结 二、const 1、const 修饰普通变量 2、const 修饰指针 3、const 修饰函数参数 4. const 修饰函数返回值 5. const 修饰类成员 6. const 与 #defi…

云计算-华为HCIA-学习笔记

笔者今年7月底考取了华为云计算方向的HCIE认证&#xff0c;回顾从IA到IE的学习和项目实战&#xff0c;想整合和分享自己的学习历程&#xff0c;欢迎志同道合的朋友们一起讨论&#xff01; 第三章&#xff1a;常见设备 交换机 二层交换机和三层交换机&#xff0c;所谓二层交换机…

基于FPGA的2FSK调制-串口收发-带tb仿真文件-实际上板验证成功

基于FPGA的2FSK调制 前言一、2FSK储备知识二、代码分析1.模块分析2.波形分析 总结 前言 设计实现连续相位 2FSK 调制器&#xff0c;2FSK 的两个频率为:fI15KHz&#xff0c;f23KHz&#xff0c;波特率为 1500 bps,比特0映射为f 载波&#xff0c;比特1映射为 载波。 1&#xff09…

网络安全与加密

1.Base64简单说明描述&#xff1a;Base64可以成为密码学的基石&#xff0c;非常重要。特点&#xff1a;可以将任意的二进制数据进行Base64编码结果&#xff1a;所有的数据都能被编码为并只用65个字符就能表示的文本文件。65字符&#xff1a;A~Z a~z 0~9 / 对文件进行base64编码…

Python绘制太极八卦

文章目录 系列目录写在前面技术需求1. 图形绘制库的支持2. 图形绘制功能3. 参数化设计4. 绘制控制5. 数据处理6. 用户界面 完整代码代码分析1. rset() 函数2. offset() 函数3. taiji() 函数4. bagua() 函数5. 绘制过程6. 技术亮点 写在后面 系列目录 序号直达链接爱心系列1Pyth…