快速排序题目SelectK问题(力扣75.颜色分类、力扣215.数组中的第K个最大元素、面试题17.14最小K个数)

news2024/10/1 15:03:21

力扣75.颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

class Solution { //时间复杂度为O(n)
    //其实,变量 zero 相当于我们在三路快速排序算法中的 lt;
    //变量 two 相当于我们在三路快速排序算法中的 gt。
    public void sortColors(int[] nums) {
        // nums[0...zero] == 0, nums[zero + 1, i] == 1, nums[two, n - 1] == 2
        // 定义三个指针:zero、i、two,分别表示0的最右边界、当前处理的元素、2的最左边界
        int zero = -1, i = 0, two = nums.length;
        while(i < two){// 当前处理元素的指针小于2的最左边界时,继续循环
            // 如果当前元素为0,将其与zero右边的元素交换,并将zero和i都向右移动一位
            if(nums[i] == 0){
                zero++;
                swap(nums,zero,i);
                i++;
            }
            // 如果当前元素为2,将其与two左边的元素交换,并将two向左移动一位
            else if (nums[i] == 2){ // 注意此时不需要i右移,因为交换后的元素还需要继续判断
                two --;
                swap(nums, i, two);
            }
            else{ //如果当前元素是1,不需要操作,直接继续向右遍历
                i ++;
            }
        }
    }
    // 交换数组中指定位置的两个元素
    private void swap(int[] nums, int i, int j){
        int t = nums[i];
        nums[i]= nums[j];
        nums[j] = t;
    }
}

关于SelectK问题:即在一个无序数组中,找出第K小(大)的元素

我们首先来封装一个 selectK 的方法。封装好了这个方法以后,这三个问题都可
以快速求解。
我们的 selectK 的接口是这样的:

// 在 arr[l...r] 的范围里求解整个数组的第 k 小元素并返回
// k 是索引,即从 0 开始计算
int selectK(int[] arr, int l, int r, int k, Random rnd)

因为我们的 partition 过程需要随机选取标定点,所以我们还需要传一个 Random(快排的优化)
类的对象 rnd。
定义好函数签名以后,下面我们来书写相应的逻辑。

  • 首先,selectK 的过程,我们就是要执行一遍 partition。在这里,我使用双路快速排序的 partition。(partition属于核心代码块要掌握)
  • 注意,因为在这个问题中,我们肯定我们处理的数据类型是 int,所以,在代码中,我不再使用泛型:
private int partition(int[] arr, int l, int r, Random rnd){
    // 生成 [l, r] 之间的随机索引
    int p = l + rnd.nextInt(r - l + 1);
    swap(arr, l, p);
    // arr[l+1...i-1] <= v; arr[j+1...r] >= v
    int i = l + 1, j = r;
    while(true){
    while(i <= j && arr[i] < arr[l])
        i ++;
    while(j >= i && arr[j] > arr[l])
        j --;
    if(i >= j) break;

    swap(arr, i, j);
    i ++;
    j --;
}
    swap(arr, l, j);
    return j;
}
private void swap(int[] arr, int i, int j){
    int t = arr[i];
    arr[i] = arr[j];
    arr[j] = t;
}

有了 partition,我们的 selectK 的主题逻辑非常简单。

首先,进行 partition,假设结果是 p。我们只需要将 k 和 p 做比较。

  • 如果 k == p,直接返回 arr[p] 即可;
  • 如果 k < p,在 arr[l, p - 1] 的范围继续找,即调用 selectK(arr, l, p - 1, k, rnd);
  • 如果 k > p,在 arr[p + 1, r] 的范围继续找,即调用 selectK(arr, p + 1, r, k, rnd);

就有了下面的代码:

private int selectK(int[] arr, int l, int r, int k, Random rnd){
int p = partition(arr, l, r, rnd);
if(k == p) return arr[p];
if(k < p) return selectK(arr, l, p - 1, k, rnd);
    return selectK(arr, p + 1, r, k, rnd);
}

这样,我们就完成了 select K 的过程。是不是非常简单!

下面,我们用我们写的 select K,先来解决 Leetcode 上第 215 号问题:

这个问题是求第 k 大元素,但是我们的 selectK 求得是第 k 小元素。怎么办?
非常简单,我们只需要在调用 select K 之前,将求第 k 大元素的这个 k,转换成
对应求的是第几小元素对应的索引就好了。
按照题目描述,如果 k 是 1,对应就是要找最大元素,那么相应的我们的
select K 的索引,就是 nums.length - 1。(如果10个数,K=1,第一个最大的数,就是SelectK索引为9的那个的元素)
如果 k 是 nums.length,其实就是求最小元素,那么相应的我们的 selectK 的
索引,就是 0。  (如果10个数,第10个最大的数,就是SelectK索引为0的那个的元素,最小值)
他们之间的转换关系是 nums.length - k。

力扣215.数组中的第K个最大元素 

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

import java.util.Random; //导入Random包
class Solution {
    public int findKthLargest(int[] nums, int k) {
        //只有两行,其他的内容全部复用我们上面实现的 selectK,是不是很酷?
        //我们的SelectK是第K最小元素,所以这里findKthLargest传入下标要处理一下
        //转换关系是 nums.length - k
        Random rnd = new Random();
        return selectK(nums, 0, nums.length - 1, nums.length - k, rnd);
    }

    //有了 partition,我们的 selectK 的主题逻辑非常简单。
    private int selectK(int[] arr, int l, int r, int k, Random rnd){
        //首先,进行 partition,假设返回值结果是 p。我们只需要将 k 和 p 做比较。
        int p = partition(arr, l, r, rnd); 
        //如果 k == p,直接返回 arr[p] 即可;
        if(k == p) return arr[p]; 
        //如果 k < p,在 arr[l, p - 1] 的范围继续找,即调用 selectK(arr, l, p - 1, k, rnd);
        if(k < p) return selectK(arr, l, p - 1, k, rnd); 
        //如果 k > p,在 arr[p + 1, r] 的范围继续找,即调用 selectK(arr, p + 1, r, k, rnd);
        return selectK(arr, p + 1, r, k, rnd);
}
    private int partition(int[] arr, int l, int r, Random rnd){
        // 生成 [l, r] 之间的随机索引
        int p = l + rnd.nextInt(r - l + 1);
        swap(arr, l, p);
        // arr[l+1...i-1] <= v; arr[j+1...r] >= v
        int i = l + 1, j = r;
        while(true){
            while(i <= j && arr[i] < arr[l])
                i ++;
            while(j >= i && arr[j] > arr[l])
                j --;
            if(i >= j) break;
            swap(arr, i, j);
            i ++;
            j --;
        }
        swap(arr, l, j);
        return j;
    }

    //数组指定索引,两数交换
    private void swap(int[] arr, int i, int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

面试题17.14最小K个数&&剑指offer40&&LCR159.库存管理

 

下面,我们解决《剑指 Offer》上的 40 号问题,最小的 k 个数: 
https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/ 
对于这个问题,我们只要使用上面的 selectK,找到第 k 小的数。
然后,selectK 的过程由于调用了 partiton,所以会调整整个数组的内容。
此时,这个第 k 小的 数的前面所有元素,就是整个数组最小的 k 个数字了。
 

这里要注意,我们求的第 k 小的数字,对应的索引是 k - 1,因为题目给的下标k从0开始

所以我们调用 selectK 的时候,需要传入的索引参数是 k - 1。 
另外,对于这个问题,k 可能取 0,此时,我们不需要执行 selectK,直接返回一 
个含有 0 个元素的new int[] 就好了。

怎么样,是不是很简单?具体过程如下:

import java.util.Arrays;
import java.util.Random;
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if(k == 0) return new int[0];
        Random rnd = new Random();
        selectK(arr, 0, arr.length - 1, k - 1, rnd);
//Arrays.copyOf()是java.util.Arrays 类中的一个方法。它会创建一个指定长度的新数组,并将原始数组中的元素复制到新数组中。
        return Arrays.copyOf(arr, k); 
    }
    private int selectK(int[] arr, int l, int r, int k, Random rnd){
        int p = partition(arr, l, r, rnd);
        if(k == p) return arr[p];
        if(k < p) return selectK(arr, l, p - 1, k, rnd);
        return selectK(arr, p + 1, r, k, rnd);
    }
    private int partition(int[] arr, int l, int r, Random rnd){
        // 生成 [l, r] 之间的随机索引
        int p = l + rnd.nextInt(r - l + 1);
        swap(arr, l, p);
        // arr[l+1...i-1] <= v; arr[j+1...r] >= v
        int i = l + 1, j = r;
        while(true){
            while(i <= j && arr[i] < arr[l])
                i ++;
            while(j >= i && arr[j] > arr[l])
                j --;
            if(i >= j) break;
            swap(arr, i, j);
            i ++;
            j --;
        }
        swap(arr, l, j);
        return j;
    }
    private void swap(int[] arr, int i, int j){
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}

如果让调用java里库的方法,先 排序 后取值就更容易,不过  有点easy,面试时可能不让用

//时间复杂度:O(nlog⁡n),其中n是数组arr的长度。算法的时间复杂度即排序的时间复杂度。
//空间复杂度:O(log⁡n),排序所需额外的空间复杂度为 O(log⁡n)。
class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] vec = new int[k];
        Arrays.sort(arr); //调用Array.sort方法,给arr从小到大排序,然后取前k个数
        for (int i = 0; i < k; ++i) {
            vec[i] = arr[i];
        }
        return vec; //返回对应数组
    }
}
我在这个问题下提交如上的代码,大概需要 7ms 左右的时间:
但是,使用我们的 selectK 的方式,大概 2ms 就能搞定。
其实,O(n) 的复杂度和 O(nlogn) 的复杂度,在大多数时候,尤其是面试或者算
法竞赛中,差距并不大。但是,当数据规模上来以后,还是能看出来的。如果
有兴趣的同学,可以在自己的计算机上,测试一下对于 100 万甚至是 1000 万级
别的数据规模,看看而这是不是差距更明显?

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

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

相关文章

使用Nexus搭建npm私服库

优质博文&#xff1a;IT-BLOG-CN 【1】下载nexus http://www.sonatype.com/download-oss-sonatype解压到本地即可&#xff1b; 【2】打开nexus-3.2.0-01-win64\nexus-3.2.0-01\bin&#xff1b;打开cmd&#xff08;必须使用cmd&#xff09; 执行nexus.exe /run&#xff1b;需要使…

贪吃蛇的简单实现(c语言)

前言&#xff1a;学完了C语言的基础语法&#xff0c;和一点数据结构的知识&#xff0c;拿贪吃蛇来练练手&#xff0c;并熟悉以前的知识。写完之后&#xff0c;有一种成就感&#xff0c;为以后的学习饱满激情。 注意这里的讲解是由部分到整体的思路。 目录 控制台不能是终端&am…

OpenCV-复数矩阵点乘ComplexMatrixDotMultiplication

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 需求说明 一般用到FFT&#xff0c;就涉及到复数的计算&#xff0c;为了便于调用&#xff0c;我自行封装了一个简单的复数矩阵点乘…

《从零开始的Java世界》08集合框架

《从零开始的Java世界》系列主要讲解Javase部分&#xff0c;从最简单的程序设计到面向对象编程&#xff0c;再到异常处理、常用API的使用&#xff0c;最后到注解、反射&#xff0c;涵盖Java基础所需的所有知识点。学习者应该从学会如何使用&#xff0c;到知道其实现原理全方位式…

YOLO世界:实时开放词汇对象检测

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;YOLO世界&#xff1a;实时开放词汇对象检测1、研究背景2、提出方法3、相关技术3.1、Re-parameterizable Vision-Language Path Ag…

react09 hooks(useState)

react-09 hooks&#xff08;useState&#xff09; hooks组件&#xff08;函数组件动态化&#xff09; 其本质就是函数组件&#xff0c;引用一些hooks方法&#xff0c;用来在函数组件中进行例如状态管理&#xff0c;模拟类组件的生命周期等&#xff0c;只能运用到函数组件中 ho…

Linux的UDEV机制

udev 机制引入&#xff1a; 手机接入Linux热拔插相关 a. 把手机接入开发板 b. 安装adb工具&#xff0c;在终端输入adb安装指令&#xff1a; sudo apt-get install adb c. dmeg能查看到手机接入的信息&#xff0c;但是输入adb devices会出现提醒 dinsufficient permissions for …

酷开会员 | 酷开系统带你足不出户获得同电影院一般的观影体验

在繁忙、混乱的快节奏工作中&#xff0c;人们总是渴望在下班后&#xff0c;逃离工作的桎梏找到一丝慰藉&#xff0c;看电影&#xff0c;则成为了很多人宣泄情感、放松心情的一种方式。但是&#xff0c;电影院的时间和地点总是那么不受控制&#xff0c;要么地点太远、要么场次不…

k8s安装,linux-ubuntu上面kubernetes详细安装过程

官方文档&#xff1a;https://kubernetes.io/zh-cn/docs/setup/production-environment/container-runtimes/ 环境配置 该部分每个主机都要执行 如果你确定不需要某个特定设置&#xff0c;则可以跳过它。 设置root登录 sudo passwd root sudo vim /etc/ssh/sshd_config Perm…

密码学 | 数字证书:应用

&#x1f951;原文&#xff1a;数字签名和数字证书的原理解读 - 知乎 &#x1f951;前文&#xff1a;密码学 | 数字签名 数字证书 - CSDN &#x1f951;提示&#xff1a;把客户端想成 Alice&#xff0c;服务器端想成 Bob 即可。客户端实际上指的是客户端浏览器。 下面&#…

Ubuntu 22最新dockers部署redis哨兵模式,并整合spring boot和配置redisson详细记录(含spring boot项目包)

dockers部署redis哨兵模式&#xff0c;并整合spring boot 环境说明相关学习博客一、在docker中安装redis1、下载dockers镜像包和redis配置文件&#xff08;主从一样&#xff09;2、编辑配置文件3、启动redis&#xff08;主从一样&#xff09;4、进入容器测试&#xff08;主从一…

PyTorch与深度学习:探索现代神经网络的魅力

在科技飞速发展的今天&#xff0c;深度学习作为人工智能领域的重要分支&#xff0c;已经在图像识别、自然语言处理、语音识别等多个领域取得了突破性的进展。而PyTorch&#xff0c;作为一款开源的深度学习框架&#xff0c;以其简洁易用、动态计算图等特性&#xff0c;赢得了广大…

react合成事件与原生事件区别备忘

朋友问起在做一个下拉框组件&#xff0c;下拉的点击事件是用react的onClick触发&#xff0c;外部区域点击关闭则用dom的原生点击事件绑定&#xff0c;问题是下拉的点击事件无法阻止冒泡到dom的原生事件。 我说&#xff0c;react的合成事件 和 原生事件是不一样的&#xff0c;尽…

【科学研究】那些考进精英大学的农家子弟们

::: block-1 “时问桫椤”是一个致力于为本科生到研究生教育阶段提供帮助的不太正式的公众号。我们旨在在大家感到困惑、痛苦或面临困难时伸出援手。通过总结广大研究生的经验&#xff0c;帮助大家尽早适应研究生生活&#xff0c;尽快了解科研的本质。祝一切顺利&#xff01;—…

Ollama教程——生成内容API:利用Ollama的原生API进行AI应用开发

相关文章: Ollama教程——入门&#xff1a;开启本地大型语言模型开发之旅 Ollama教程——模型&#xff1a;如何将模型高效导入到ollama框架 Ollama教程——兼容OpenAI API&#xff1a;高效利用兼容OpenAI的API进行AI项目开发 Ollama教程——使用langchain&#xff1a;ollama与…

iTwin Capture Modeler-23中文版下载地址及安装教程

文章目录 一、iTwin Capture Modeler23中文版安装教程二、iTwin Capture Modeler23中文版下载地址一、iTwin Capture Modeler23中文版安装教程 1. 解压安装包。订阅专栏(可获取专栏内所有文章阅读权限与软件安装包)后,从文末获取安装包解压,如下所示: 2. 右击安装包,选择以…

火绒安全的详细用法

1. 引言 本章将介绍火绒安全软件的基本概述和用法。火绒安全是一款功能强大的安全软件,提供了多种保护功能和工具,可以帮助您保护计算机免受恶意软件、网络攻击和其他安全威胁的侵害。 2. 火绒安全的功能 火绒安全具有以下主要功能: 实时防护:火绒安全提供实时监测和防护…

9.Eureka服务发现+Ribbon+RestTemplate服务调用

order-service服务通过服务名称来代替 ip:port的方式访问user-service服务的接口。 原来的请求代码&#xff1a; Service public class OrderServiceImpl implements OrderService {Autowiredprivate OrderMapper orderMapper;Autowiredprivate RestTemplate restTemplate;Ov…

修改Ubuntu22.04系统图标

在Ubuntu 22.04中更改开机时显示的Ubuntu图标需要修改的设置。 主要思路是用自己图片替换系统图片&#xff0c;保持系统图片同名&#xff0c;同格式。 以下是一般的步骤&#xff1a; 修改启动界面的logo&#xff1a; sudo cp 新logo.png /usr/share/plymouth/themes/spinn…

江苏鲁岳轴承制造有限公司:石墨轴承与耐高温轴承的制造翘楚

耐高温轴承的优质生产地&#xff1a;江苏鲁岳轴承制造有限公司。石墨轴承与耐高温轴承的完美生产商&#xff1a;江苏鲁岳轴承制造有限公司的专业承诺。 石墨轴承是一种具有独特耐高温性能的轴承产品&#xff0c;广泛应用于各种高温、高速、高负载的工业环境中。江苏鲁岳轴承制…