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

目录

详解快速排序

面试题 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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣---腐烂的橘子

题目&#xff1a; bfs思路&#xff1a; 感觉bfs还是很容易想到的&#xff0c;首先定义一个双端队列&#xff08;队列也是可以的~&#xff09;&#xff0c;如果值为2&#xff0c;则入队列&#xff0c;我这里将队列中的元素定义为pair<int,int>。第一个int记录在数组中的位…

嵌入式驱动学习第二周——使用perf进行性能优化

前言 这篇博客来聊一聊如何使用perf进行性能优化。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可以关注本博主并订阅本专栏&#xff0c;一起讨论一起学习。现在关注就是老粉啦&#xff01; 目录 前言…

考研复习C语言初阶(4)+标记和BFS展开的扫雷游戏

目录 1. 一维数组的创建和初始化。 1.1 数组的创建 1.2 数组的初始化 1.3 一维数组的使用 1.4 一维数组在内存中的存储 2. 二维数组的创建和初始化 2.1 二维数组的创建 2.2 二维数组的初始化 2.3 二维数组的使用 2.4 二维数组在内存中的存储 3. 数组越界 4. 冒泡…

HarmonyOS NEXT应用开发之MpChart图表实现案例

介绍 MpChart是一个包含各种类型图表的图表库&#xff0c;主要用于业务数据汇总&#xff0c;例如销售数据走势图&#xff0c;股价走势图等场景中使用&#xff0c;方便开发者快速实现图表UI。本示例主要介绍如何使用三方库MpChart实现柱状图UI效果。如堆叠数据类型显示&#xf…

FPGA的配置状态字寄存器Status Register

目录 简介 状态字定义 Unknown Device/Many Unknow Devices 解决办法 一般原因 简介 Xilinx的FPGA有多种配置接口&#xff0c;如SPI&#xff0c;BPI&#xff0c;SeletMAP&#xff0c;Serial&#xff0c;JTAG等&#xff1b;如果从时钟发送者的角度分&#xff0c;还可以…

2024/3/10周报

文章目录 摘要Abstract文献阅读题目问题创新点方法Section1&#xff1a;运动员检测Section2&#xff1a;行为识别输入层隐藏层输出层 实验实验数据评估指标模型设置实验结果 深度学习模糊逻辑系统概念模糊化模糊规则解模糊 总结 摘要 本周阅读了一篇关于基于YOLO和深度模糊LST…

131.分割回文串

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

【Objective -- C】—— 自引用计数

【Objective -- C】—— 自引用计数 一. 内存管理/自引用计数1.自引用计数2.内存管理的思考方式自己生成的对象&#xff0c;自己持有非自己生成的对象&#xff0c;自己也能持有不再需要自己持有的对象时释放无法释放非自己持有的对象 3.alloc/retain/release/dealloc实现4. aut…

力扣--滑动窗口438.找到字符串中所有字母异位词

思路分析&#xff1a; 使用两个数组snum和pnum分别记录字符串s和p中各字符出现的次数。遍历字符串p&#xff0c;统计其中各字符的出现次数&#xff0c;存储在pnum数组中。初始化snum数组&#xff0c;统计s的前m-1个字符的出现次数。从第m个字符开始遍历s&#xff0c;通过滑动窗…

《YOLO5Face: Why Reinventing a Face Detector》为什么要重塑人脸检测器论文阅读

正好周末的时间天气也不错出去走走精神不错&#xff0c;回来读一篇论文这个论文之前查资料的时候看到的但是没有完整看下&#xff0c;今天正好花点时间整体看一下&#xff0c;下面是我自己阅读过程中使用翻译软件结合自己理解的阅读记录&#xff0c;感兴趣的话可以看下&#xf…

知识图谱 | 2023年图书馆学、情报学CSSCI期刊论文主题透视

数据来源 检索平台来源期刊年份有效数据中国知网大学图书馆学报国家图书馆学刊情报科学情报理论与实践情报学报情报杂志情报资料工作数据分析与知识发现图书馆建设图书馆论坛图书馆学研究图书馆杂志图书情报工作图书情报知识图书与情报现代情报信息资源管理学报中国图书馆学报2…

性能测试高阶内容:了解TPS和RT之间关系

引言 在开始今天的内容讲解之前&#xff0c;我们应该回顾一下&#xff0c;在我的全链路压测专栏中的第一篇&#xff0c;我就已经介绍了当前的性能测试在互联网企业中的重要性&#xff0c;已经性能在互联网行业中的占比是多少。 这个时候是不是会有同学问我&#xff0c; 你已经…

JVM-1

目录 1.基础知识 1.栈 2.本地方法栈 3.程序计数器 4.堆 5.方法区 6.JVM内存可见性 2.虚拟机类加载机制 1.加载 2.验证 3.准备 4.解析 5.初始化 6.使用 7.卸载 1.基础知识 JVM内存模型&#xff08;5种&#xff09;&#xff1a;栈&#xff0c;本地方法栈&#xff…

Java项目:44 ssm003在线医疗服务系统+jsp(含文档)

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 主要功能 前台登录&#xff1a; 注册用户&#xff1a;用户名、密码、姓名、联系电话 注册医生&#xff1a;医生工号、密码、医生姓名、职称、联系电话…

【Python】专栏文章索引

为了方便 快速定位 和 便于文章间的相互引用等 作为一个快速准确的导航工具 Python 目录&#xff1a; &#xff08;一&#xff09;装饰器函数 &#xff08;二&#xff09;牛客网—软件开发-Python专项练习 &#xff08;三&#xff09;time模块

数据结构与算法第三套试卷

1.删除链表节点 **分析&#xff1a;**首先用指针变量q指向结点A的后继结点B&#xff0c;然后将结点B的值复制到结点A中&#xff0c;最后删除结点B。 2.时间复杂度的计算 **分析&#xff1a;**当涉及嵌套循环的时候&#xff0c;我们可以直接分析内层循环即可&#xff0c;看内…

猫头虎分享已解决Bug || 批处理错误:BatchJobFailure, ProcessingDelay

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

太阳辐射环境模拟系统系统

太阳辐射环境模拟系统是一种高度专业化的设备&#xff0c;用于模拟太阳光的全谱段辐射&#xff0c;包括紫外线、可见光和红外线。这种系统的核心功能是在实验室条件下复制太阳的辐射条件&#xff0c;以评估材料、产品或设备在实际太阳辐射影响下的性能和耐久性。 应用领域&…

“比特币深夜冲破7万美元”!华尔街押注比特币:究竟是牛市墙头草,还是加密真信徒?

比特币ETF&#xff0c;使此次加密牛市与以往的繁荣、萧条周期截然不同。以往的周期往往由热衷风险的投机者以及最终崩盘的加密项目所驱动&#xff0c;例如无实物资产支持的加密货币借贷&#xff0c;以及一地鸡毛的ICO热潮。而现在&#xff0c;传统金融已经与加密世界联姻&#…

前端手册-实现挂坠灯笼效果

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…