第k个数
第K个数
给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数。
所用方法和基本原理
快速选择算法是基于快速排序思想的一种选择算法。其基本原理如下:
- 划分操作:选择一个基准元素(这里选择数组中间位置的元素),通过双指针法将数组分为两部分,使得左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素。
- 确定目标位置:统计左边部分的元素个数。如果 k 小于等于左边部分元素个数,说明第 k 小的数在左边部分,继续在左边部分进行快速选择;否则,第 k 小的数在右边部分,需要在右边部分进行快速选择,并且此时 k 的值要减去左边部分的元素个数。
- 递归求解:不断重复上述过程,直到找到第 k 小的数。
代码及注释
public class QuickSelect {
// 快速选择函数,用于找到数组中第k小的数
public static int quickSort(int[] arr, int l, int r, int k) {
// 如果左右指针重合,说明只有一个元素,直接返回该元素
if (l == r) return arr[l];
// 选择中间位置的元素作为基准元素x
int x = arr[l + (r - l >> 1)];
// 初始化左指针i,指向数组起始位置的前一个
int i = l - 1;
// 初始化右指针j,指向数组末尾位置的后一个
int j = r + 1;
// 当左指针小于右指针时,继续循环进行划分
while (i < j) {
// 从左向右移动左指针,找到第一个大于等于基准元素的位置
while (arr[++i] < x);
// 从右向左移动右指针,找到第一个小于等于基准元素的位置
while (arr[--j] > x);
// 如果左指针仍小于右指针,说明两个指针未相遇,交换两个指针所指向的元素
if (i < j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
// 计算左边部分(包括基准元素所在位置)的元素个数
int lNum = j - l + 1;
// 如果k大于左边部分元素个数,说明第k小的数在右边部分,递归在右边部分查找
if (k > lNum) {
return quickSort(arr, j + 1, r, k - lNum);
} else {
// 否则,第k小的数在左边部分,递归在左边部分查找
return quickSort(arr, l, j, k);
}
}
}
举例说明
假设有数组 arr = [3, 2, 1, 5, 6, 4],要找第 3 小的数(即 k = 3)。
- 第一次划分:
- 选择基准元素
x = arr[2] = 1(中间位置)。 - 初始化
i = -1,j = 6。 - 移动
i到位置 0(因为arr[0] = 3 > 1),移动j到位置 5(因为arr[5] = 4 > 1)。 - 交换
arr[0]和arr[5],数组变为[4, 2, 1, 5, 6, 3]。 - 继续移动
i到位置 1(因为arr[1] = 2 > 1),移动j到位置 2(因为arr[2] = 1)。 - 此时
i = 1,j = 2,左边部分元素个数lNum = 2 - 0 + 1 = 3。 - 因为
k = 3,所以第 k 小的数在左边部分,继续在左边部分[4, 2, 1]进行查找。
- 选择基准元素
- 第二次划分:
- 选择基准元素
x = arr[1] = 2(中间位置)。 - 初始化
i = -1,j = 2。 - 移动
i到位置 0(因为arr[0] = 4 > 2),移动j到位置 1(因为arr[1] = 2)。 - 交换
arr[0]和arr[1],数组变为[2, 4, 1]。 - 继续移动
i到位置 1(因为arr[1] = 4 > 2),移动j到位置 0(因为arr[0] = 2)。 - 此时
i = 1,j = 0,左边部分元素个数lNum = 0 - 0 + 1 = 1。 - 因为
k = 3,k > lNum,所以第 k 小的数在右边部分[4, 1]进行查找,并且k变为3 - 1 = 2。
- 选择基准元素
- 第三次划分:
- 选择基准元素
x = arr[1] = 1(中间位置)。 - 初始化
i = -1,j = 1。 - 移动
i到位置 0(因为arr[0] = 4 > 1),移动j到位置 1(因为arr[1] = 1)。 - 交换
arr[0]和arr[1],数组变为[1, 4]。 - 继续移动
i到位置 0(因为arr[0] = 1),移动j到位置 0(因为arr[0] = 1)。 - 此时
i = 0,j = 0,左边部分元素个数lNum = 0 - 0 + 1 = 1。 - 因为
k = 2,k > lNum,所以第 k 小的数在右边部分[4]进行查找,并且k变为2 - 1 = 1。
- 选择基准元素
- 第四次划分:
- 此时数组只有一个元素
[4],l = r = 0,直接返回arr[0] = 4,找到第 3 小的数为 4。
- 此时数组只有一个元素
方法的优劣
- 时间复杂度:
- 平均情况:快速选择算法平均时间复杂度为 $O(n)$,因为每次划分平均可以排除一半的数据。
- 最坏情况:时间复杂度为 $O(n^2)$,当每次选择的基准元素都是数组中的最大或最小元素时,划分会极不均匀,导致每次只能排除一个元素。
- 空间复杂度:
- 平均情况:空间复杂度为 $O(\log n)$,这是由于递归调用栈的深度平均为 $\log n$。
- 最坏情况:空间复杂度为 $O(n)$,在最坏情况下,递归调用栈的深度为 $n$。
优点:平均情况下时间复杂度为线性,效率较高,适用于大规模数据的选择问题。
缺点:最坏情况下时间复杂度退化到 $O(n^2)$,并且算法不稳定,即相同元素的相对顺序在排序前后可能会改变。
第k个数的更多相关文章
- 剑指Offer面试题:27.最小的k个数
一.题目:最小的k个数 题目:输入n个整数,找出其中最小的k个数.例如输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3.4. 这道题是典型的TopK问题,其最简单的思路莫过于 ...
- 算法系列:寻找最大的 K 个数
Copyright © 1900-2016, NORYES, All Rights Reserved. http://www.cnblogs.com/noryes/ 欢迎转载,请保留此版权声明. -- ...
- 算法练习:寻找最小的k个数
参考July的文章:http://blog.csdn.net/v_JULY_v/article/details/6370650 寻找最小的k个数题目描述:查找最小的k个元素题目:输入n个整数,输出其中 ...
- 剑指Offer:面试题30——最小的k个数(java实现)
问题描述: 输入n个整数,找出其中最小的k个数 思路1: 先排序,再取前k个 时间复杂度O(nlogn) 下面给出快排序的代码(基于下面Partition函数的方法) public void Quic ...
- 输入一个数组,求最小的K个数
被这道题困了好久,看了剑指Offer才知道OJ上的要求有点迷惑性. 题目: 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4. 一 ...
- 【编程之美】2.5 寻找最大的k个数
有若干个互不相等的无序的数,怎么选出其中最大的k个数. 我自己的方案:因为学过找第k大数的O(N)算法,所以第一反应就是找第K大的数.然后把所有大于等于第k大的数取出来. 写这个知道算法的代码都花了2 ...
- 1046: 最小的K个数
1046: 最小的K个数 时间限制: 1 Sec 内存限制: 128 MB提交: 233 解决: 200[提交][状态][讨论版] 题目描述 输入n个整数,找出其中最小的K个数.例如输入4,5,1 ...
- 最小的K个数:用快排的思想去解相关问题
实现快速排序算法的关键在于先在数组中选择一个数字,接下来把数组中的数字分为两部分,比选择的数字小的数字移到数组的左边,比选择的数字大的数字移到数组的右边. 这个函数可以如下实现: int Partit ...
- [转载]寻找两个有序数组中的第K个数或者中位数
http://blog.csdn.net/realxie/article/details/8078043 假设有长度分为为M和N的两个升序数组A和B,在A和B两个数组中查找第K大的数,即将A和B按升序 ...
- 剑指offer面试题30:最小的k个数
一.题目描述 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. 二.解题思路 1.思路1 首先对数组进行排序,然后取出前k个数 ...
随机推荐
- Redis 通用命令
KEYS 语法: KEYS pattern 功能: 返回所有匹配 pattern 的键 可以使用该命令的Redis版本: 1.0.0 时间复杂度: O(N) N指的是在数据库中的键的数量 不建议在生成 ...
- MySQL函数-根据子节点查询所有父节点名称
背景 公司的一个业务系统中有区域表,整个区域是一个树结构,为了方便根据某一父节点查询所有叶子节点,提供了一个额外的字段path,按照分隔符存储了从根节点到当前节点的总路径. 表结构如下: create ...
- Java WatchService监控指定路径下的文件新增、删除和修改(子文件夹、指定文件类型)
WatchService 是 Java NIO 包 (java.nio.file) 中提供的一个用于监控文件系统变化的 API.它允许应用程序监听目录中的文件创建.修改和删除事件. 基本原理 Watc ...
- SQL Server 5105 和 1802 错误的触发方式和解决方式之一
一般导致这两个错误的原因是:文件路径错误 还有的说,可能是文件权限问题,详情见权限错误纠正方式 错误代码 create database teaching on primary ( name = te ...
- xe10.3+paserver在Ubuntu下运行错误
xe.3的paserver在Ubuntu下执行呈现乱七八糟的错误提示. 原因:Ubuntu的版本和paserver编译的环境不一致. 注意:使用ARM64的版本.如ubuntu-18.04.2-des ...
- 中文Markmap v2.0 现已上线,新增高效功能,老板再也留不住你下班的脚步!
介绍 Markmap.js 是一款开源项目,在 GitHub 上获得了超过 1.7 万个星的关注,它的主要功能是将 Markdown 文档可视化为思维导图. 在日常使用中,用户经常需要面对老板的&qu ...
- mysql分区自动维护(SpringBoot+MybatisPlus)
1.环境 SpringBoot + MybatisPlus + MySQL 2.简介 通过定时器@Scheduled每日触发,查询当前库中所有分区表(这里以时间段进行分区) 判断剩余分区是否小于自定义 ...
- this 和super 关键字的区别
this关键字 (1) 每个类的每个非静态方法(没有被static修饰)都会隐含一个this关键字,它指向调用这个方法的对象:当在方法中使用本类属性时,都会隐含地使用this关键字,当然也可以明确使用 ...
- 工具 | ysoSimple
0x00 简介 ysoSimple是一款简易的Java漏洞利用工具,集成Java反序列化,Hessian反序列化,XStream反序列化,SnakeYaml反序列化,Shiro550,JSF反序列化, ...
- centos7部署keepalived
yum install keepalived -y 修改/etc/keepalived.conf配置文件,达到高可用状态 vim /etc/keepalived/keepalived.conf ! C ...