《剑指 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;elseright = 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.hqwc.cn/news/526001.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux多线程编程使用互斥量的原理分析和应用实例

目录 概述 1 保护对共享变量的访问&#xff1a;互斥量 1.1 认识互斥量 1.2 互斥锁API 1.2.1 互斥锁初始化函数 1.2.2 互斥锁函数 1.2.3 互斥锁变体函数 1.3 互斥锁使用方法 1.4 互斥锁死锁 2 互斥量的应用介绍 2.1 创建与销毁 2.1.1 创建互斥量 2.1.2 销毁互斥量 …

React组件(函数式组件,类式组件)

函数式组件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>React Demo</title> <!-- 引…

【牛客】VL69 脉冲同步器(快到慢)

描述 sig_a 是 clka&#xff08;300M&#xff09;时钟域的一个单时钟脉冲信号&#xff08;高电平持续一个时钟clka周期&#xff09;&#xff0c;请设计脉冲同步电路&#xff0c;将sig_a信号同步到时钟域 clkb&#xff08;100M&#xff09;中&#xff0c;产生sig_b单时钟脉冲信号…

基于php的用户登录实现(v2版)(持续迭代)

目录 版本说明 数据库连接 登录页面&#xff1a;login.html 登录处理实现&#xff1a;login.php 用户欢迎页面&#xff1a;welcome.php 密码修改页面&#xff1a;change_password.html 修改执行&#xff1a;change_password.php 用户注册页面&#xff1a;register.html …

推荐算法中经典排序算法GBDT+LR

文章目录 逻辑回归模型逻辑回归对于特征处理的优势逻辑回归处理特征的步骤 GBDT算法GBDTLR算法GBDT LR简单代码实现 逻辑回归模型 逻辑回归&#xff08;LR,Logistic Regression&#xff09;是一种传统机器学习分类模型&#xff0c;也是一种比较重要的非线性回归模型&#xff0…

FPGA的时钟资源

目录 简介 Clock Region详解 MRCC和SRCC的区别 BUFGs 时钟资源总结 简介 7系列FPGA的时钟结构图&#xff1a; Clock Region&#xff1a;时钟区域&#xff0c;下图中有6个时钟区域&#xff0c;用不同的颜色加以区分出来 Clock Backbone&#xff1a;从名字也能看出来&#x…

【C语言】linux内核ip_local_out函数

一、讲解 这个函数 __ip_local_out 是 Linux 内核网络子系统中的函数&#xff0c;部分与本地出口的 IPv4 数据包发送相关。下面讲解这段代码的每一部分&#xff1a; 1. 函数声明 int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)&#xff1a; -…

Hive安装教程-Hadoop集成Hive

文章目录 前言一、安装准备1. 安装条件2. 安装jdk3. 安装MySQL4. 安装Hadoop 二、安装Hive1. 下载并解压Hive2. 设置环境变量3. 修改配置文件3. 创建hive数据库4. 下载MySQL驱动5. 初始化hive数据库6. 进入Hive命令行界面7. 设置允许远程访问 总结 前言 本文将介绍安装和配置H…

Spring web开发(入门)

1、我们在执行程序时&#xff0c;运行的需要是这个界面 2、简单的web接口&#xff08;127.0.0.1表示本机IP&#xff09; package com.example.demo;import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestCont…

【sgExcelGrid】自定义组件:简单模拟Excel表格拖拽、选中单元格、横行、纵列、拖拽圈选等操作

特性&#xff1a; 可以自定义拖拽过表格可以点击某个表格&#xff0c;拖拽右下角小正方形进行任意方向选取单元格支持选中某一行、列支持监听selectedGrids、selectedDatas事件获取选中项的DOM对象和数据数组支持props自定义显示label字段别名 sgExcelGrid源码 <template&g…

SQLite3中的callback回调函数注意的细节

调用 sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg)该例程提供了一个执行 SQL 命令的快捷方式&#xff0c; SQL 命令由 sql 参数提供&#xff0c;可以由多个 SQL 命令组成。 在这里&#xff0c; 第一个参数 sqlite3 是打开的数据库对…

HarmonyOS NEXT应用开发案例——列表编辑实现

介绍 本示例介绍用过使用ListItem组件属性swipeAction实现列表左滑编辑效果的功能。 该场景多用于待办事项管理、文件管理、备忘录的记录管理等。 效果图预览 使用说明&#xff1a; 点击添加按钮&#xff0c;选择需要添加的待办事项。长按待办事项&#xff0c;点击删除后&am…