【LeetCode排序专题02】最小k个数,关于快速排序的讨论
最小k个数
https://leetcode.cn/problems/smallest-k-lcci/
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
快速排序
快排是一种冒泡法的优化版本,逻辑上使用了分治思想,代码实现上使用了递归的方式,直接上例子来说
以下是一个无序数组,使用快排对其进行排序,核心代码逻辑如右侧所示

默认以left初始时指向的最左侧元素为基准值
基准值也称为pivot,即"轴"。不断移动左右指针,与轴比较,比轴大的放在轴的右边,比轴小的放在轴的左边
在这里,pivot = 2,即nums[0]
此时满足最外层循环条件,进入循环

大循环内还有两个循环,用于移动左右指针
当前右指针大于基准值pivot(5 > 2),移动right,到3处,还是大,继续移动
到0处不满足条件,执行下一个小循环,判断左指针移动情况

当前左指针等于基准值pivot(2 = 2),满足第二个小循环条件,移动left
此时左指针等于4,大于基准值2,退出小循环,继续执行后面的语句

此时,将左右指针指向的值进行交换
当前左右指针并没有相交,因此继续执行大循环内的两个小循环

(后面的过程同理,就不画图了)
右指针的值还是大于pivot,right左移
此时不满足条件,往后执行第二个小循环
左指针的值小于pivot(0 < 1),left右移
左右指针相交,大循环结束,此时两指针相交的位置就是当前pivot需要移动到的位置

如图所示,pivot(2)移动到相交处
此时,我们就以pivot为基准对数组进行了第一次划分
总结一下过程:
1、先确定基准值,一般用数组最左边的值。
2、然后用右指针和基准值比较,如果右指针指向的值大就不用动当前指向值,向左移动右指针,继续比较,直到右指针指向的值小于基准值(注意,此时还不能交换左右指针值),开始移动左指针
3、使用左指针的值与基准值比较,如果左指针指向的值小就不用动当前指向值,向右移动左指针,继续比较,直到左指针指向的值大于基准值,此时再交换左右指针的值
4、完成一轮移动,如果当前左右指针相交了就结束循环,最左边的基准值和当前相交位置的值互换
5、此时数组被分为左右区间,分别在这两个区间触发递归,继续排序
可见,关键点在于:一定要右指针、左指针都移动完毕之后再交换左右指针指向的值,然后再判断左右指针是否相交
继续
当前,pivot(2)左边的都是小于2的数,pivot(2)右边的都是大于2的数

然后,对当前pivot划分的左右区间再次进行划分(方法相同)
这里就是快排需要使用递归的原因,我们需要不断的划分剩余的区间,直到最后排好序
下面来看代码实现
代码分析
凡是涉及递归的都可以按照三部曲来写
1、确认递归函数的参数和返回值
这里是对数组进行排序操作,不需要返回值;
输入参数为待排序的数组以及需要排序的区间
class Solution {
private:
//对数组进行排序操作,不需要返回值,输入参数为待排序的数组以及需要排序的区间
void quickSort(vector<int>& arr, int left, int right){
}
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
}
};
2、确定终止条件
终止条件肯定是左右指针相交
class Solution {
private:
//对数组进行排序操作,不需要返回值,输入参数为待排序的数组以及需要排序的区间
void quickSort(vector<int>& arr, int left, int right){
//确定终止条件
if(left > right) return;
}
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
}
};
3、处理单层递归逻辑
在代码实现时,我们可以直接将left指针指向的值视为pivot(也就是说不用单独定义一个pivot变量,一是影响性能,二是在交换变量时容易逻辑混乱)
循环之前,定义两个循环变量i、j分别初始化为left和right的值
class Solution {
private:
//对数组进行排序操作,不需要返回值,输入参数为待排序的数组以及需要排序的区间
void quickSort(vector<int>& arr, int left, int right){
//确定终止条件
if(left >= right) return;
//处理单层逻辑
//单独定义循环变量
int i = left, j = right;
// int pivot = left;//不用多余再定义一个变量
while(i < j){
//右指针指向的数要大于基准值的话就不用动,仅移动指针(此时,基准值由left充当)
while(i < j && arr[j] >= arr[left]) j--;
while(i < j && arr[i] <= arr[left]) i++;//同理
swap(arr[i], arr[j]);//不满足上述条件就交换
//将right指向的但是小的数放到pivot左边,将left指向的但是大的数放到pivot右边
}//大循环结束,此时左右指针相交,把pivot移动到相交位置
swap(arr[i], arr[left]);//写j也行
//此时已经将数组分为左区间(小于pivot)和右区间(大于pivot)
//调用递归对左右区间再次进行划分,直到排序完成
quickSort(arr, left, i - 1);//左区间递归排序(左指针left重置为数组最左边元素)
quickSort(arr, i + 1, right);//右区间递归(右指针right重置为数组最右边元素)
}
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
}
};
完整代码
class Solution {
private:
//对数组进行排序操作,不需要返回值,输入参数为待排序的数组以及需要排序的区间
void quickSort(vector<int>& arr, int left, int right){
//确定终止条件
if(left >= right) return;
//处理单层逻辑
int i = left, j = right;
// int pivot = left;//不用多余再定义一个变量
while(i < j){
//右指针指向的数要大于基准值的话就不用动,仅移动指针(此时,基准值由left充当)
while(i < j && arr[j] >= arr[left]) j--;
while(i < j && arr[i] <= arr[left]) i++;//同理
swap(arr[i], arr[j]);//不满足上述条件就交换(即右指针、左指针按顺序移动完毕)
//将right指向的但是小的数放到pivot左边,将left指向的但是大的数放到pivot右边
}//大循环结束,此时左右指针相交,把pivot移动到相交位置
swap(arr[i], arr[left]);//写j也行
//此时已经将数组分为左区间(小于pivot)和右区间(大于pivot)
//调用递归对左右区间再次进行划分,直到排序完成
quickSort(arr, left, i - 1);//左区间递归排序(左指针left重置为数组最左边元素)
quickSort(arr, i + 1, right);//右区间递归(右指针right重置为数组最右边元素)
}
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
//调用递归函数
quickSort(arr, 0, arr.size() - 1);
vector<int> res(arr.begin(), arr.begin() + k);//排序后,取数组中"前k大的元素"构成结果数组
return res;
}
};
优化版
这里其实有个可以优化的点
在每次划分完毕后,基准值都会在arr[i]的位置(i为循环变量)
因为题目要求是:返回数组中“前k个最小的数”,只要返回就行,这些数有无顺序均可以
因此可以根据k与i的关系来决定是否继续排序
- 若k < i,代表第 k + 1 小的数字在 左子数组 中,则递归左子数组
- 若k > i,代表第 k + 1 小的数字在 右子数组 中,则递归右子数组
- 若k = i,代表此时
arr[k]即为第 k + 1小的数字,则直接返回数组前 k 个数字即可;
在代码上,需要把递归函数的返回值改为数组,因为要依据i与k的大小关系来决定递归左区间还是右区间,并且,k也需要作为参数输入到递归函数中
TBD
【LeetCode排序专题02】最小k个数,关于快速排序的讨论的更多相关文章
- 求n个数中的最大或最小k个数
//求n个数中的最小k个数 public static void TestMin(int k, int n) { Random rd = new Ra ...
- nyoj 678 最小K个数之和
最小K个数之和 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描述 输入n个整数,输出其中最小的K个数之和.例如输入4,5,1,1,6,2,7,3,3这9个数字,当k=4 ...
- 最小k个数
题目 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. 思考 方法0: 直接排序然后返回前k个,最好的时间复杂度为 O(nlo ...
- 算法试题 - 找出最小 k 个数
题目 题目:输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. 解析 思路1 这一题应用堆排序算法复杂度只有O(nlog k), ...
- 最小K个数之和
描述 输入n个整数,输出其中最小的K个数之和.例如输入4,5,1,1,6,2,7,3,3这9个数字,当k=4,则输出最小的4个数之和为7(1,1,2,3). 输入 测试样例组数不超过10 每个测试案例 ...
- 【13】堆排序 最小K个数
题目 输入整数数组 arr ,找出其中最小的 k 个数.例如,输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3.4. 收获 优先队列实现 (n1,n2)->n2-n1是 ...
- 将一个整数数组先按照因子数量排序,再按照数字大小排序,输出第k个数
同小米OJ比赛题:现在有 n 个数,需要用因子个数的多少进行排序,因子个数多的排在后面,因子个数少的排在前面,如果因子个数相同那么就比较这个数的大小,数大的放在后面,数小的放在前面.现在让你说出排序之 ...
- 编程算法 - 最小的k个数 代码(C)
最小的k个数 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 输入n个整数, 找出当中的最小k个数. 使用高速排序(Quick Sort)的方法 ...
- 剑指 Offer 40. 最小的k个数 + 优先队列 + 堆 + 快速排序
剑指 Offer 40. 最小的k个数 Offer_40 题目描述 解法一:排序后取前k个数 /** * 题目描述:输入整数数组 arr ,找出其中最小的 k 个数.例如,输入4.5.1.6.2.7. ...
- 剑指Offer28 最小的K个数(Partition函数应用+大顶堆)
包含了Partition函数的多种用法 以及大顶堆操作 /*********************************************************************** ...
随机推荐
- [转帖]如何在本地编译安装部署自动化回归测试平台 AREX
https://zhuanlan.zhihu.com/p/613877597 AREX 官方 QQ 交流群:656108079 本文将详细为大家介绍一下自动化回归测试平台 AREX 以及如何在本地进行 ...
- Linux 清理 防火墙已有IP地址的方法
最简单的处理 for i in `firewall-cmd --zone=trusted --list-sources` ;do firewall-cmd --zone=trusted --remov ...
- 【K哥爬虫普法】老铁需要车牌靓号吗?判刑的那种
我国目前并未出台专门针对网络爬虫技术的法律规范,但在司法实践中,相关判决已屡见不鲜,K 哥特设了"K哥爬虫普法"专栏,本栏目通过对真实案例的分析,旨在提高广大爬虫工程师的法律意识, ...
- 结构体定义及结构体粒度(alignment)
结构体定义及结构体粒度(alignment) #pragma pack(1) typedef struct _STUDENT_INFORMATION_ { int Age; char v1; int ...
- 3.0 熟悉IDAPro静态反汇编器
IDA Pro 是一种功能强大且灵活的反汇编工具,可以在许多领域中发挥作用,例如漏洞研究.逆向工程.安全审计和软件开发等,被许多安全专家和软件开发者用于逆向工程和分析二进制代码.它支持大量的二进制文件 ...
- 集成Unity3D到iOS应用程序中
如果想让原生平台(例如 Java/Android.Objective C/iOS 或 Windows Win32/UWP)包含 Unity 功能,可以通过Unity 生成UnityFramework静 ...
- Docker从认识到实践再到底层原理(三)|Docker在Centos7环境下的安装和配置
前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总 然后就是博主最近最花时间的一 ...
- RocketMQ—RocketMQ发送同步、异步、单向、延迟、批量、顺序、批量消息、带标签消息
RocketMQ-RocketMQ发送同步.异步.单向.延迟.批量.顺序.批量消息.带标签消息 发送同步消息 生产者发送消息,mq进行确认,然后返回给生产者状态.这就是同步消息. 前文demo程序就是 ...
- 48从零开始用Rust编写nginx,搭建一个简单又好看官方网站
wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 负载均衡, 静态文件服务器,websocket代理,四层TCP/UDP转发,内网穿透等,会将实 ...
- DBGRIDEH 底部多列 发现
1.设置底部行数 2.点击footers 单独对每一行进行设置 3.单独对这两行 进行设置 5.看下辅助 所以用的时候可以这样用:WeiTopTradeShow.FieldColumns['top_x ...