第K个数

给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数。

所用方法和基本原理

快速选择算法是基于快速排序思想的一种选择算法。其基本原理如下:

  1. 划分操作:选择一个基准元素(这里选择数组中间位置的元素),通过双指针法将数组分为两部分,使得左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素。
  2. 确定目标位置:统计左边部分的元素个数。如果 k 小于等于左边部分元素个数,说明第 k 小的数在左边部分,继续在左边部分进行快速选择;否则,第 k 小的数在右边部分,需要在右边部分进行快速选择,并且此时 k 的值要减去左边部分的元素个数。
  3. 递归求解:不断重复上述过程,直到找到第 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)。

  1. 第一次划分

    • 选择基准元素 x = arr[2] = 1(中间位置)。
    • 初始化 i = -1j = 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 = 1j = 2,左边部分元素个数 lNum = 2 - 0 + 1 = 3
    • 因为 k = 3,所以第 k 小的数在左边部分,继续在左边部分 [4, 2, 1] 进行查找。
  2. 第二次划分
    • 选择基准元素 x = arr[1] = 2(中间位置)。
    • 初始化 i = -1j = 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 = 1j = 0,左边部分元素个数 lNum = 0 - 0 + 1 = 1
    • 因为 k = 3k > lNum,所以第 k 小的数在右边部分 [4, 1] 进行查找,并且 k 变为 3 - 1 = 2
  3. 第三次划分
    • 选择基准元素 x = arr[1] = 1(中间位置)。
    • 初始化 i = -1j = 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 = 0j = 0,左边部分元素个数 lNum = 0 - 0 + 1 = 1
    • 因为 k = 2k > lNum,所以第 k 小的数在右边部分 [4] 进行查找,并且 k 变为 2 - 1 = 1
  4. 第四次划分
    • 此时数组只有一个元素 [4]l = r = 0,直接返回 arr[0] = 4,找到第 3 小的数为 4。

方法的优劣

  1. 时间复杂度

    • 平均情况:快速选择算法平均时间复杂度为 $O(n)$,因为每次划分平均可以排除一半的数据。
    • 最坏情况:时间复杂度为 $O(n^2)$,当每次选择的基准元素都是数组中的最大或最小元素时,划分会极不均匀,导致每次只能排除一个元素。
  2. 空间复杂度
    • 平均情况:空间复杂度为 $O(\log n)$,这是由于递归调用栈的深度平均为 $\log n$。
    • 最坏情况:空间复杂度为 $O(n)$,在最坏情况下,递归调用栈的深度为 $n$。

优点:平均情况下时间复杂度为线性,效率较高,适用于大规模数据的选择问题。

缺点:最坏情况下时间复杂度退化到 $O(n^2)$,并且算法不稳定,即相同元素的相对顺序在排序前后可能会改变。

第k个数的更多相关文章

  1. 剑指Offer面试题:27.最小的k个数

    一.题目:最小的k个数 题目:输入n个整数,找出其中最小的k个数.例如输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3.4. 这道题是典型的TopK问题,其最简单的思路莫过于 ...

  2. 算法系列:寻找最大的 K 个数

    Copyright © 1900-2016, NORYES, All Rights Reserved. http://www.cnblogs.com/noryes/ 欢迎转载,请保留此版权声明. -- ...

  3. 算法练习:寻找最小的k个数

    参考July的文章:http://blog.csdn.net/v_JULY_v/article/details/6370650 寻找最小的k个数题目描述:查找最小的k个元素题目:输入n个整数,输出其中 ...

  4. 剑指Offer:面试题30——最小的k个数(java实现)

    问题描述: 输入n个整数,找出其中最小的k个数 思路1: 先排序,再取前k个 时间复杂度O(nlogn) 下面给出快排序的代码(基于下面Partition函数的方法) public void Quic ...

  5. 输入一个数组,求最小的K个数

    被这道题困了好久,看了剑指Offer才知道OJ上的要求有点迷惑性. 题目: 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4. 一 ...

  6. 【编程之美】2.5 寻找最大的k个数

    有若干个互不相等的无序的数,怎么选出其中最大的k个数. 我自己的方案:因为学过找第k大数的O(N)算法,所以第一反应就是找第K大的数.然后把所有大于等于第k大的数取出来. 写这个知道算法的代码都花了2 ...

  7. 1046: 最小的K个数

    1046: 最小的K个数 时间限制: 1 Sec  内存限制: 128 MB提交: 233  解决: 200[提交][状态][讨论版] 题目描述 输入n个整数,找出其中最小的K个数.例如输入4,5,1 ...

  8. 最小的K个数:用快排的思想去解相关问题

    实现快速排序算法的关键在于先在数组中选择一个数字,接下来把数组中的数字分为两部分,比选择的数字小的数字移到数组的左边,比选择的数字大的数字移到数组的右边. 这个函数可以如下实现: int Partit ...

  9. [转载]寻找两个有序数组中的第K个数或者中位数

    http://blog.csdn.net/realxie/article/details/8078043 假设有长度分为为M和N的两个升序数组A和B,在A和B两个数组中查找第K大的数,即将A和B按升序 ...

  10. 剑指offer面试题30:最小的k个数

    一.题目描述 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. 二.解题思路 1.思路1 首先对数组进行排序,然后取出前k个数 ...

随机推荐

  1. windows端5款mysql客户端工具

    1. MySQL Workbench 这属于mysql官方出品,免费,功能强大,是首选. 2. HeidiSQL 免费,功能强大,强烈推荐. 3. dbForge Studio for MySQL 收 ...

  2. 在Winform开发框架支持多种数据库基础上,增加对国产数据库人大金仓的支持

    一个良好的产品,可能往往需要支持多种数据库的接入,根据实际业务的需要进行调整,有时候可能需要2到3种数据库的支持. 在很多应用系统里面,虽然一般采用一种数据库运行,但是由于各种情况的需要,可能业务系统 ...

  3. 🎀Java-Exception与RuntimeException

    简介 Exception Exception 类是所有非致命性异常的基类.这些异常通常是由于编程逻辑问题或外部因素(如文件不存在.网络连接失败等)导致的,可以通过适当的编程手段来恢复或处理.Excep ...

  4. MCP数据脱敏应用开发

    一.概述 数据脱敏(Data Masking),又称数据漂白.数据去隐私化或数据变形. 定义 指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护.在涉及客户安全数据或者一些商业性敏 ...

  5. MySQL 中有哪些锁类型?

    MySQL 中有哪些锁类型? 在 MySQL 中,锁是用于管理并发访问的机制,以保证数据一致性和完整性.MySQL 支持多种类型的锁,按照其粒度和用途可以分为以下几类. 1. 按粒度分类 表锁(Tab ...

  6. 【doctrine/orm】findBy用法

    用法: //$condition array('表字段对应的entity的属性'=>'值') //$orderBy array('表字段'=>'ASC/DESC') //$count in ...

  7. EFCore学习(二)——添加,修改,删除,查询操作及将EFCore语句编译成sql

    实质: EFCore的底层实际是将关于实体类的的操作编译成sql,然后让ado.net去执行 在Program.cs里使用SchoolContext 说明:需要SchoolContext.cs声明实体 ...

  8. 17.6K star!后端接口零代码的神器来了,腾讯开源的ORM库太强了!

    嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 " 实时零代码.全功能.强安全 ORM 库 后端接口和文档零代码,前端定制返回 J ...

  9. DialogHub上线OpenHarmony开源社区,高效开发鸿蒙应用弹窗

    作为鸿蒙应用开发者,在使用ArkUI现有能力进行弹窗开发时,总会遇到一些让人纠结的交互问题:应用内进行消息提示时,既要求消息内容支持图文混排,又要求弹窗本身不能打断用户交互(页面滑动.页面点击.键盘输 ...

  10. 仿EXCEL插件,智表ZCELL产品V2.0 版本发布,优化全键盘操作,增加JSON格式导入导出功能

    详细请移步 智表(ZCELL)官网www.zcell.net 更新说明  这次更新主要应用户要求,主要一方面重构了底层,优化了键盘操作,支持全键盘录入,另一方面增加了JSON格式的导入导出,支持终端用 ...