《剑指 Offer》专项突破版 - 面试题 76 : 数组中第 k 大的数字(C++ 实现)

news2025/2/27 23:55:57

目录

详解快速排序

面试题 76 : 数组中第 k 大的数字


 


详解快速排序

快速排序是一种非常高效的算法,从其名字可以看出这种排序算法最大的特点是快。当表现良好时,快速排序的速度比其他主要对手(如归并排序)快 2 ~ 3 倍。

快速排序的基本思想是分治法,排序过程如下所示:在输入数组中随机选取一个元素作为中间值(pivot),然后对数组进行分区(partition),使所有比中间值小的数据移到数组的左边,所有比中间值大的数据移到数组的右边。接下来对中间值左右两侧的子数组用相同的步骤顺序,直到子数组中只有一个数字为止

理解快速排序的关键在于理解它的分区的过程。下面以数组 [4, 1, 5, 3, 6, 2, 7, 8] 为例分析分区的过程。假设数字 3 被随机选中为中间值,该数字被交换到数组的尾部。接下来初始化两个指针,指针 P1 初始化至下标为 -1 的位置,指针 P2 初始化至下标为 0 的位置,如下图 (a) 所示。始终将指针 P1 指向已经发现的最后一个小于 3 的数字。此时尚未发现任何一个小于 3 的数字,因此将指针 P1 指向一个无效的位置。将指针 P2 从下标为 0 的位置开始向右扫描数组中的每个数字。当指针 P2 指向第 1 个小于 3 的数字 1 时,指针 P1 向右移动一格,然后交换两个指针指向的数字,此时数组(即两个指针)的状态如下图 (b) 所示。继续向右移动指针 P2 直到遇到下一个小于 3 的数字 2,指针 P1 再次向右移动一格,然后交换两个指针指向的数字,此时数组(即两个指针)的状态如下图 (c) 所示。继续向右移动指针 P2 直到指向数字 3 也没有遇到新的小于 3 的数字,此时整个数组已经扫描完毕。再次将指针 P1 向右移动一格,然后交换指针 P1 和 P2 指向的数字,于是所有小于 3 的数字都位于 3 的左边,所有大于 3 的数字都位于 3 的右边,如下图 (d) 所示。

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        srand((unsigned int)time(0));
        quickSort(nums, 0, nums.size() - 1);
        return nums;
    }
private:
    void quickSort(vector<int>& nums, int left, int right) {
        if (left >= right)
            return;
        
        int pivotLoc = partition(nums, left, right);
        quickSort(nums, left, pivotLoc - 1);
        quickSort(nums, pivotLoc + 1, right);
    }
​
    int partition(vector<int>& nums, int left, int right) {
        int random = left + rand() % (right - left + 1);
        swap(nums[random], nums[right]);
​
        int prev = left - 1, cur = left;
        while (cur < right)
        {
            if (nums[cur] < nums[right] && ++prev != cur)
                swap(nums[prev], nums[cur]);
            
            ++cur;
        }
        ++prev;
        swap(nums[prev], nums[right]);
        return prev;
    }
};

快速排序的时间复杂度取决于所选取的中间值在数组中的位置。如果每次选取的中间值在排序数组中都接近于数组中间的位置,那么快速排序的时间复杂度是 O(nlogn)。如果每次选取的中间值都位于排序数组的头部或尾部,那么快速排序的时间复杂度是 O(n^2)。这也是随机选取中间值的原因,避免在某些情况下快速排序退化成时间复杂度为 O(n^2) 的算法。由此可知,在随机选取中间值的前提下,快速排序的平均复杂度是 O(nlogn),是非常高效的排序算法

很多面试官喜欢要求应聘者手写快速排序算法的代码,因此应聘者需要深刻理解快速排序的思想及分区的过程,这样在遇到要求手写快速排序的代码时也能心中有底。另外,快速排序中的 partition 函数还经常被用来选中数组中第 k 大的数字,而这也是一道非常经典的算法面试题


面试题 76 : 数组中第 k 大的数字

题目

请从一个乱序数组中找出第 k 大的数字。例如,数组 [3, 1, 2, 4, 5, 5, 6] 中第 3 大的数字是 5。

分析

面试题 59 中介绍过一种基于最小堆的解法,该解法的时间复杂度是 O(nlogk)。下面介绍一种更快的解法。

在长度为 n 的排序数组中,第 k 大的数字的下标是 n - k。下面用快速排序的函数 partition 对数组进行分区。

  1. 如果函数 partition 选取的中间值在分区之后的下标正好是 n - k,分区后左边的值都比中间值小,右边的值都比中间值大,即使整个数组不是排序的,中间值也肯定是第 k 大的数字

  2. 如果函数 partition 选取的中间值在分区之后的下标大于 n - k,那么第 k 大的数字一定位于中间值的左侧,于是再对中间值左侧的子数组分区

  3. 如果函数 partition 选取的中间值在分区之后的下标小于 n - k,那么第 k 大的数字一定位于中间值的右侧,于是再对中间值右侧的子数组分区

代码实现

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        srand((unsigned int)time(0));
​
        int target = nums.size() - k;
        int left = 0, right = nums.size() - 1;
        int pivotLoc = partition(nums, left, right);
        while (pivotLoc != target)
        {
            if (pivotLoc < target)
                left = pivotLoc + 1;
            else
                right = pivotLoc - 1;
            
            pivotLoc = partition(nums, left, right);
        }
        return nums[pivotLoc];
    }
private:
    int partition(vector<int>& nums, int left, int right) {
        int random = left + rand() % (right - left + 1);
        swap(nums[random], nums[right]);
​
        int prev = left - 1, cur = left;
        while (cur < right)
        {
            if (nums[cur] < nums[right] && ++prev != cur)
                swap(nums[prev], nums[cur]);
            
            ++cur;
        }
        ++prev;
        swap(nums[prev], nums[right]);
        return prev;
    }
};

由于函数 partition 随机选择中间值,因此它的返回值也具有随机性,计算这种算法的时间复杂度需要运用概率相关的知识。此处仅计算一种特定场合下的时间复杂度。假设函数 partition 每次选择的中间值都位于分区后的数组的中间的位置,那么第 1 次函数 partition 需要扫描长度为 n 的数组,第 2 次需要扫描长度为 n/2 的子数组,第 3 次需要扫描 n/4 的子数组,重复这个过程,直到子数组的长度为 1。由于 n + n/2 + n/4 + ··· + 1 = 2n,因此总的时间复杂度是 O(n)

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

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

相关文章

WordPress高端后台美化WP Adminify Pro优化版

后台UI美化WP Adminify Pro修改自定义插件&#xff0c;适合建站公司和个人使用&#xff0c;非常高大上&#xff0c;下载地址&#xff1a;WP Adminify Pro优化版 修复记录&#xff1a; 1、修复已知BUG 2、修复手机版兼容问题 3、修复打开速度&#xff0c;原版打开速度太慢 4…

Git的基本操作(安装Git,创建本地仓库,配置Git,添加、修改、回退、撤销修改、删除文件)

文章目录 一、Git安装二、创建本地仓库三、配置Git四、认识工作区、暂存区、本地库五、添加文件六、修改文件七、版本回退八、撤销修改1.对于⼯作区的代码&#xff0c;还没有add2.已经add&#xff0c;但没有commit3.已经add&#xff0c;并且已经commit 九、删除⽂件 一、Git安装…

解释区块链技术的应用场景、优势及经典案例

目录 1.区块链应用场景 2.区块链优势 3.区块链经典案例 区块链技术是一种分布式账本技术&#xff0c;它通过加密和安全验证机制&#xff0c;允许网络中的多个参与者之间进行可信的、不可篡改的交易和数据的记录与传输。区块链技术的应用场景广泛&#xff0c;其优势也十分显著…

R语言复现:中国Charls数据库一篇现况调查论文的缺失数据填补方法

编者 在临床研究中&#xff0c;数据缺失是不可避免的&#xff0c;甚至没有缺失&#xff0c;数据的真实性都会受到质疑。 那我们该如何应对缺失的数据&#xff1f;放着不管&#xff1f;还是重新开始?不妨试着对缺失值进行填补&#xff0c;简单又高效。毕竟对于统计师来说&#…

【AcWing】蓝桥杯集训每日一题Day1|二分|差分|503.借教室(C++)

503. 借教室 503. 借教室 - AcWing题库难度&#xff1a;简单时/空限制&#xff1a;1s / 128MB总通过数&#xff1a;8052总尝试数&#xff1a;26311来源&#xff1a;NOIP2012提高组算法标签二分差分 题目内容 在大学期间&#xff0c;经常需要租借教室。 大到院系举办活动&…

Yolov8模型用torch_pruning剪枝

目录 &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680;&#x1f680; 原理 遍历所有分组 高级剪枝器 &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680…

TYPE C模拟耳机POP音产生缘由

关于耳机插拔的POP音问题&#xff0c;小白在之前的文章中讲述过关于3.5mm耳机的POP音产生原因。其实这类插拔问题的POP音不仅仅存在于3.5mm耳机&#xff0c;就连现在主流的Type C模拟耳机的插拔也存在此问题&#xff0c;今天小白就来讲一讲这类耳机产生POP音的缘由。 耳机左右…

计算机视觉——P2PNet基于点估计的人群计数原理与C++模型推理

简介 人群计数是计算机视觉领域的一个核心任务&#xff0c;旨在估算静止图像或视频帧中的行人数量。在过去几十年中&#xff0c;研究人员在这个领域投入了大量的精力&#xff0c;并在提高现有主流基准数据集性能方面取得了显著进展。然而&#xff0c;训练卷积神经网络需要大规…

书与我

和书深深结缘&#xff0c;始于需求&#xff0c;得益于通勤时间长。 读什么书 一直没有停止过编码&#xff0c;工作性质也要求我必须了解很多的新技术&#xff0c;从踏上工作岗位后&#xff0c;就需要不停的看书。从《JAVA编程思想》、《java与模式》、《TCP/IP详解》、《深入…

131.分割回文串

// 定义一个名为Solution的类 class Solution {// 声明一个成员变量&#xff0c;用于存储所有满足条件的字符串子序列划分结果List<List<String>> lists new ArrayList<>(); // 声明一个成员变量&#xff0c;使用LinkedList实现的双端队列&#xff0c;用于临…

Windows下安装pip

一、下载pip 官网地址&#xff1a;https://pypi.org/project/pip/#files 1.1、pip工具查找方法 单击官网首页“PyPi”选项 在弹出来的搜索框中输入“pip” 选择最新的pip版本&#xff0c;点进去 下载pip安装包包 二、安装pip 解压“pip-24.0.tar.gz”&#xff0c;进…

【深度学习笔记】6_5 RNN的pytorch实现

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.5 循环神经网络的简洁实现 本节将使用PyTorch来更简洁地实现基于循环神经网络的语言模型。首先&#xff0c;我们读取周杰伦专辑歌词…

b站小土堆pytorch学习记录—— P23-P24 损失函数、反向传播和优化器

文章目录 一、损失函数1.简要介绍2.代码 二、优化器1.简要介绍2.代码 一、损失函数 1.简要介绍 可参考博客&#xff1a; 常见的损失函数总结 损失函数的全面介绍 pytorch学习之十九种损失函数 损失函数&#xff08;Loss Function&#xff09;是用来衡量模型预测输出与实际…

开发指南002-前后端信息交互规范-概述

前后端之间采用restful接口&#xff0c;服务和服务之间使用feign。信息交互遵循如下平台规范&#xff1a; 前端&#xff1a; 建立api目录&#xff0c;按照业务区分建立不同的.js文件&#xff0c;封装对后台的调用操作。其中qlm*.js为平台预制的接口文件&#xff0c;以qlm_user.…

离线数仓(五)【数据仓库建模】

前言 今天开始正式数据仓库的内容了, 前面我们把生产数据 , 数据上传到 HDFS , Kafka 的通道都已经搭建完毕了, 数据也就正式进入数据仓库了, 解下来的数仓建模是重中之重 , 是将来吃饭的家伙 ! 以及 Hive SQL 必须熟练到像喝水一样 ! 第1章 数据仓库概述 1.1 数据仓库概念 数…

【stm32 外部中断】

中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行 中断优先级&#xff1a;当有多个中…

mybatis-plus整合spring boot极速入门

使用mybatis-plus整合spring boot&#xff0c;接下来我来操作一番。 一&#xff0c;创建spring boot工程 勾选下面的选项 紧接着&#xff0c;还有springboot和依赖我们需要选。 这样我们就创建好了我们的spring boot&#xff0c;项目。 简化目录结构&#xff1a; 我们发现&a…

未来城市:探索数字孪生在智慧城市中的实际应用与价值

目录 一、引言 二、数字孪生与智慧城市的融合 三、数字孪生在智慧城市中的实际应用 1、智慧交通管理 2、智慧能源管理 3、智慧建筑管理 4、智慧城市管理 四、数字孪生在智慧城市中的价值 五、挑战与展望 六、结论 一、引言 随着科技的飞速发展&#xff0c;智慧城市已…

R统计学2 - 数据分析入门问题21-40

往期R统计学文章&#xff1a; R统计学1 - 基础操作入门问题1-20 21. 如何对矩阵按行 (列) 作计算&#xff1f; 使用函数 apply() vec 1:20 # 转换为矩阵 mat matrix (vec , ncol4) # [,1] [,2] [,3] [,4] # [1,] 1 6 11 16 # [2,] 2 7 12 17 # [3,] …

前端框架的发展历史介绍

前端框架的发展历史是Web技术进步的一个重要方面。从最初的简单HTML页面到现在的复杂单页应用程序&#xff08;SPA&#xff09;&#xff0c;前端框架和库的发展极大地推动了Web应用程序的构建方式。以下是一些关键的前端框架和库&#xff0c;以及它们的发布年份、创建者和主要特…