无序数组求第K大的数
问题描述
无序数组求第K大的数,其中K从1开始算。
例如:[0,3,1,8,5,2]这个数组,第2大的数是5
OJ可参考:LeetCode_0215_KthLargestElementInAnArray
堆解法
设置一个小根堆,先把前K个数放入小根堆,对于这前K个数来说,堆顶元素一定是第K大的数,接下来的元素继续入堆,但是每入一个就弹出一个,最后,堆顶元素就是整个数组的第K大元素。代码如下:
public static int findKthLargest3(int[] nums, int k) {
PriorityQueue<Integer> h = new PriorityQueue<>();
int i = 0;
// 经历这个循环,前K个数的第K大的数就是h的堆顶元素
while (i < k) {
h.offer(nums[i++]);
}
// 每次入一个,出一个,这样就保证了堆顶元素永远保持第K大的元素
while (i < nums.length) {
h.offer(nums[i++]);
h.poll();
}
return h.peek();
}
由于每次堆需要logK的调整代价, 所以这个解法的时间复杂度为O(N*logK)
改进快排算法
快速排序中,有一个partition的过程, 代码如下,注:以下代码是从大到小排序的partition过程
private static int[] partition(int[] nums, int l, int r, int pivot) {
int i = l;
int more = l - 1;//大于区域
int less = r + 1; // 小于区域
while (i < less) {
if (nums[i] > pivot) {
swap(nums, i++, ++more);
} else if (nums[i] < pivot) {
swap(nums, i, --less);
} else {
i++;
}
}
return new int[]{more + 1, less - 1};
}
这个过程主要的作用是将nums数组的l...r区间内的数,将:
小于pivot的数放右边
大于pivot的数放左边
等于pivot的数放中间
返回两个值,一个是左边界和一个右边界,位于左边界和右边界的值均等于pivot,小于左边界的位置的值都大于pivot,大于右边界的位置的值均小于pivot。简言之:如果要排序,pivot这个值在一次partition以后,所在的位置就是最终排序后pivot应该在的位置。
所以,如果数组中某个数在经历上述partion之后正好位于K-1位置,那么这个数就是整个数组第K大的数。
完整代码如下:
public class LeetCode_0215_KthLargestElementInAnArray {
// 快排改进算法
// 第K小 == 第 nums.length - k + 1 大
public static int findKthLargest2(int[] nums, int k) {
return p(nums, 0, nums.length - 1, k - 1);
}
// nums在L...R范围上,如果要排序(从大到小)的话,请返回index位置的值
public static int p(int[] nums, int L, int R, int index) {
if (L == R) {
return nums[L];
}
int pivot = nums[L + (int) (Math.random() * (R - L + 1))];
int[] range = partition(nums, L, R, pivot);
if (index >= range[0] && index <= range[1]) {
return pivot;
} else if (index < range[0]) {
return p(nums, L, range[0] - 1, index);
} else {
return p(nums, range[1] + 1, R, index);
}
}
private static int[] partition(int[] nums, int l, int r, int pivot) {
int i = l;
int more = l - 1;//大于区域
int less = r + 1; // 小于区域
while (i < less) {
if (nums[i] > pivot) {
swap(nums, i++, ++more);
} else if (nums[i] < pivot) {
swap(nums, i, --less);
} else {
i++;
}
}
return new int[]{more + 1, less - 1};
}
public static void swap(int[] nums, int t, int m) {
int tmp = nums[m];
nums[m] = nums[t];
nums[t] = tmp;
}
}
其中p方法表示:nums在L...R范围上,如果要排序(从大到小)的话,请返回index位置的值。
int pivot = nums[L + (int) (Math.random() * (R - L + 1))];
这一行表示随机取一个值pivot出来,用这个值做后续的partition操作,如果index恰好在pivot这个值做partition的左右边界范围内,则pivot就是排序后第index+1大的数(从1开始算)。
bfprt算法
brfpt算法和改进快排算法主流程上基本一致,只是在选择pivot的时候有差别,快排改进是随机取一个数作为pivot, 而bfprt算法是根据一定的规则取pivot,伪代码表示为:
public class LeetCode_0215_KthLargestElementInAnArray {
public static int findKthLargest2(int[] nums, int k) {
return bfprt(nums, 0, nums.length - 1, k - 1);
}
// nums在L...R范围上,如果要排序(从大到小)的话,请返回index位置的值
public static int bfprt(int[] nums, int L, int R, int index) {
if (L == R) {
return nums[L];
}
//int pivot = nums[L + (int) (Math.random() * (R - L + 1))];
int pivot = medianOfMedians(nums, L, R);
int[] range = partition(nums, L, R, pivot);
if (index >= range[0] && index <= range[1]) {
return pivot;
} else if (index < range[0]) {
return bfprt(nums, L, range[0] - 1, index);
} else {
return bfprt(nums, range[1] + 1, R, index);
}
}
....
}
其中
int pivot = medianOfMedians(nums, L, R);
就是bfprt算法最关键的步骤,mediaOfMedians这个函数表示:
将
num分成每五个元素一组,不足一组的补齐一组,并对每组进行排序(由于固定是5个数一组进行排序,所以排序的时间复杂度O(1)),取出每组的中位数,组成一个新的数组, 对新的数组求其中位数,这个中位数就是我们需要的值pivot。
public static int medianOfMedians(int[] arr, int L, int R) {
int size = R - L + 1;
int offSize = size % 5 == 0 ? 0 : 1;
int[] mArr = new int[size / 5 + offSize];
for (int i = 0; i < mArr.length; i++) {
// 每一组的第一个位置
int teamFirst = L + i * 5;
int median = getMedian(arr, teamFirst, Math.min(R, teamFirst + 4));
mArr[i] = median;
}
return bfprt(mArr, 0, mArr.length - 1, (mArr.length - 1) / 2);
}
public static int getMedian(int[] arr, int L, int R) {
Arrays.sort(arr, L, R);
return arr[(R + L) / 2];
}
注:mediaOfMedians方法中最后一句:
return bfprt(mArr, 0, mArr.length - 1, (mArr.length - 1) / 2);
就是利用bfprt算法拿整个元素中间位置的值。
关于bfprt算法的两个问题
为什么是5个一组
为什么严格收敛到O(N)
请参考:
三种解法复杂度分析
| 算法 | 时间 | 空间 |
|---|---|---|
| 堆 | O(N*logK) | O(N) |
| 快排改进 | 概率上收敛到:O(N) | O(1) |
| bfprt | 严格收敛到:O(N) | O(N) |
相关题目
LeetCode_0004_MedianOfTwoSortedArrays
第K小的数值对
长度为N的数组arr,一定可以组成
N^2个数值对。例如arr = [3,1,2],数值对有(3,3) (3,1) (3,2) (1,3) (1,1) (1,2) (2,3) (2,1) (2,2),也就是任意两个数都有数值对,而且自己和自己也算数值对。数值对怎么排序?规定,第一维数据从小到大,第一维数据一样的,第二维数组也从小到大。所以上面的数值对排序的结果为:(1,1)(1,2)(1,3)(2,1)(2,2)(2,3)(3,1)(3,2)(3,3), 给定一个数组arr,和整数k,返回第k小的数值对。
更多
参考资料
无序数组求第K大的数的更多相关文章
- 无序数组求第k大/第k小的数
根据http://www.cnblogs.com/zhjp11/archive/2010/02/26/1674227.html 博客中所总结的7种解法,我挑了其中的解法3和解法6进行了实现. 解法3: ...
- 查找无序数组中第K大的数
思路: 利用快速排序的划分思想 可以找出前k大数,然后不断划分 直到找到第K大元素 代码: #include <iostream> #include <algorithm> # ...
- 无序数组中第K大的数
1. 排序法 时间复杂度 O(nlogn) 2. 使用一个大小为K的数组arr保存前K个最大的元素 遍历原数组,遇到大于arr最小值的元素时候,使用插入排序方法,插入这个元素 时间复杂度,遍历是 O( ...
- 无序数组中第Kth大的数
题目:找出无序数组中第Kth大的数,如{63,45,33,21},第2大的数45. 输入: 第一行输入无序数组,第二行输入K值. 该是内推滴滴打车时(2017.8.26)的第二题,也是<剑指of ...
- 《数据结构与算法分析:C语言描述》读书笔记------练习1.1 求第K大的数
求一组N个数中的第k个最大者,设k=N/2. import java.util.Random; public class K_Max { /** * @param args */ //求第K大的数,保 ...
- [经典算法题]寻找数组中第K大的数的方法总结
[经典算法题]寻找数组中第K大的数的方法总结 责任编辑:admin 日期:2012-11-26 字体:[大 中 小] 打印复制链接我要评论 今天看算法分析是,看到一个这样的问题,就是在一堆数据 ...
- HDU 5249 离线树状数组求第k大+离散化
KPI Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...
- 查找数组中第k大的数
问题: 查找出一给定数组中第k大的数.例如[3,2,7,1,8,9,6,5,4],第1大的数是9,第2大的数是8-- 思考:1. 直接从大到小排序,排好序后,第k大的数就是arr[k-1]. 2. ...
- poj 2985 The k-th Largest Group 树状数组求第K大
The k-th Largest Group Time Limit: 2000MS Memory Limit: 131072K Total Submissions: 8353 Accepted ...
随机推荐
- 卓越的教练是如何训练高手的?(谈谈memmove)
编者按:我们知道,优秀的运动员除了自身的天赋和努力之外,出色的教练必不可少.一个成功的企业除了拥有出类拔萃的员工之外,同样需要一位出色的教练,那就是企业的CEO.由此可见,如果我们要想成为一位优秀的程 ...
- GraphQL API vs REST API
REST是构建API的一种流行方法,而且比GraphQL应用更广泛,让我们看看GraphQL和REST的区别. Rest是一个概念 REST是一个事实上的架构标准,但它实际上没有规范,有大量的非官方定 ...
- Sqli-Labs less32-37
Less-32 前置基础知识:宽字节注入 参考链接: https://blog.csdn.net/helloc0de/article/details/76180190 https://blog.csd ...
- OSPF的Router-Id
一.实验拓扑 二.实验编址 三.实验步骤: 1.设置PC的IP等信息 2.启动设备(全选) 3.根据实验编址配置路由器端口IP(先不设置lookback端口) R1: R2: R3: R4: 看一下r ...
- 带头撸抽奖系统,DDD + RPC 开发分布式架构!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.咋,撸个项目? 总有粉丝伙伴问傅哥,有没有能上手练习技术的项目,现在学了这么多技术知识.看 ...
- Arduino连接L298n驱动板驱动小车的电机
1.L298N介绍 先来讲讲电机驱动,驱动一般使用L298N,L298N 是一种双H桥电机驱动芯片,其中每个H桥可以提供2A的电流,功率部分的供电电压范围是2.5-48v,逻辑部分5v供电,接受5vT ...
- .Net 5 新特性之--支持字典在迭代中进行修改
我们都知道以前字典迭代中是不支持动态修改的[否则会报错::"Collection was modified; enumeration operation may not execute.&q ...
- QT中的对象模型――QPointer
QPointer是一个模板类,为QObject对象提供了守卫指针(Guarded Pointer).什么是守卫指针?守卫指针QPointer<T>类似于普通C++指针T *,有且仅有一点不 ...
- 【REST】使用RestSharp 库消费Restful Service
使用RestSharp 库消费Restful Service 现在互联网上的服务接口都是Restful的,SOAP的Service已经不是主流..NET/Mono下如何消费Restful Serv ...
- IO流(File类--递归--过滤器--IO字节流--IO字符流--Properties集合--缓冲流--转换流--序列化流--打印流)
一.File类 1.1概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. 1.1.1相对路径与绝对路径 相对路径从盘符开始的路径,这是一个完整 ...