【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函数的多种用法 以及大顶堆操作 /*********************************************************************** ...
随机推荐
- 【转帖】淫技巧 | 如何查看已连接的wifi密码
主题使用方法:https://github.com/xitu/juejin-markdown-themes theme: juejin highlight: github 一.引言 在实际工作中,常常 ...
- UOS关闭激活提示: Your system is not activated. Please activate as soon as possible for normal use.
最近公司里面进行UOS的兼容性验证,但是系统总是会提示: Your system is not activated. Please activate as soon as possible for n ...
- 【一个构想】pull方式获取expoter上的数据,如何更加精简?
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu 公众号:一本正经的瞎扯 背景 已知:在prometheus中,每个业务节点通过prometheu ...
- 用户 'NT Service\SSISScaleOutMaster140' 登录失败
用户 'NT Service\SSISScaleOutMaster140' 登录失败. 原因: 找不到与提供的名称匹配的登录名. 项目情况: 用户 'NT Service\SSISScaleOutMa ...
- windows幻灯片壁纸
设置为10秒 win+r输入regedit 查找路径 HKEY_CURRENT_USER\Control Panel\Personalization\Desktop Slideshow 修改inter ...
- TienChin-课程管理-添加课程页面
course.js 将 activity 替换成 course. index.vue 这个 index.vue 是 course 文件夹下面的 index.vue 别弄错了. <template ...
- .NET 6 使用 System.Drawing.Common 出现 The type initializer for ‘Gdip’ threw an exception 异常的解决办法
出现问题的原因 在Linux环境部署.NET Core程序时,如果要到System.Drawing.Common引用会出现该问题,目前大量的第三方组件使用该Windows专用库,尤其是涉及图片处理.W ...
- 配置VSFTP文件服务器
FTP 文件传输协议.用于互联网上的控制文件的双向传输,使用FTP来传输时,其实是具有一定程度的危险性,因为数据在因特网上面是完全没有受到保护的明文传输方式,VSFTP是一个基于GPL发布的类Unix ...
- P9549 「PHOI-1」路虽远 题解
题目链接:路虽远 带限制的 dijkstra,优先考虑有哪些限制条件,当做类似 dp 去写.闯黄灯次数有要求,限制速度的边数量有要求. 我们注意到,如果选择哪些边限速不易于基于贪心选择,可以考虑转换下 ...
- 关于行结束符(CR、LF)、回车、换行
CR(Carriage Return)表示回车 LF(Line Feed)表示换行 Dos和Windows采用回车+换行(CR+LF)表示下一行而UNIX/Linux采用换行符(LF)表示下一行苹果机 ...