【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函数的多种用法 以及大顶堆操作 /*********************************************************************** ...
随机推荐
- SQL注入payload学习整理
SQLserver 用的payload 0101%'and 1=(select @@version) and '%'=' GS的一个客户端参数 <add PropertyName="F ...
- 使用Git 命令行拉取、提交、推送、合并 代码
1.拉取 1.1.拉取该分支的最新代码(远程分支是与当前分支相同) git pull origin updateCode 1.2.拉取最新代码(远程分支是与当前分支不相同,但要合并) git pull ...
- tortoisesvn中看到的版本号和svn info不一致
tortoisesvn中看到的版本号和svn info不一致 在svn命令行中通过svn info命令获得的版本号与tortoisesvn中show log看到的不一样,原因是在小乌龟中可以只更新具体 ...
- 批量修改SVN的用户名和密码的尝试
起源 公司规定每6个月需要修改一次密码,否则每天都有邮件和内网提醒.因为邮箱密码和svn等一系列应用绑定,避免每次修改密码后需要手工输入修改多个svn仓库的帐号和密码. PS.同一个前缀的svn不用重 ...
- .NET 6 使用 System.Drawing.Common 出现 The type initializer for ‘Gdip’ threw an exception 异常的解决办法
出现问题的原因 在Linux环境部署.NET Core程序时,如果要到System.Drawing.Common引用会出现该问题,目前大量的第三方组件使用该Windows专用库,尤其是涉及图片处理.W ...
- Azure - 机器学习:创建机器学习所需资源,配置工作区
本文中你可以创建使用 Azure 机器学习所需的资源,包含工作区和计算实例. 关注TechLead,分享AI全维度知识.作者拥有10+年互联网服务架构.AI产品研发经验.团队管理经验,同济本复旦硕,复 ...
- DBSAT脚本快速收集方法
DBSAT是Oracle官方提供的脚本,用于数据库的安全评估检查,用户可以放心下载使用. 下载链接具体参见MOS: Oracle Database Security Assessment Tool ( ...
- SpringMVC关于@RequestBody加与不加的区别
SpringMVC关于@RequestBody加与不加的区别 前两天在做项目的时候遇到了这样一个问题,小组成员为了方便做接口测试,给Controller控制器上加了@RequestBody注解,但是前 ...
- NC16857 [NOI1999]生日蛋糕
题目链接 题目 题目描述 7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体. 设从下往上数第i(1 ≤ i ≤ M)层蛋糕是半径为Ri, 高度为Hi ...
- SSD接口与协议
该图来源于<Linux开源存储全栈详解:从Ceph到容器存储>- 2.3 存储接口协议的演变 物理接口: 从物理形态上确定各种不同的接口(引脚形式等完全不同) 传输协议: 以SATA为例, ...