寻找最大的K个数: 终极解决方案
2017-05-19 00:00
274 查看
(终于等到你)
写在前面的一句话:那些年,虐过你的面试题是:_______。
今天继续和大家聊寻找最大的K个数。
先来熟悉一下问题:
有很多个无序的数(我们这里假设为正整数),而且各不相等,怎么选出最大的K个数。
例如:2,5,7,1,3,9,3,6,7,8,5
最大的5个数为:7,9,6,7,8
之前我们给出了四种解法:
快速排序和选择排序,文章详情:寻找最大的K个数:快排和选择(一)
快排优化和二分搜索,文章详情:寻找最大的K个数:快排优化和二分搜索(二)
今天我们继续。开始前,先聊一会。
最终的那个方案,时间复杂度是线性的,也就是说,效率是六个解法中最高的。但是,它有自己的限制。
首先,所有的N个数都是正整数。
其次,它们的取值范围都不太大。
这里想说一个什么问题呢?有所长,必有所短!
老天给你打开了一扇窗,一定会给你关闭一扇门。
老天给你打开了一扇门,一定会给你打开一扇窗。
如果你好,那么你一定有某些限制;如果你慢,那么你一定有其它优点。请相信,只要存在,就是合理。
拿世界上最好的编程语言php来说(没有之一),随修改,随运行,那叫一个6666。但是缺点也是很多,至于什么,请自行百度。
道理很简单,如果一个语言,又快,又好,那么其它编程语言就可以去死了。现在其它语言都活的好好的。
所以,在什么情况下选择什么语言,是非常明智的,也是聪明的。不要一味地抱残守缺!
其实我很不理解一件事,就是面试问那么多高深的技术,进去之后还是去写业务代码。门槛如此之高,进去之初很容易摔跤。
先来说说最小堆的事。
假如,我们有一个大小为K的数组,数组是一个最小堆(数组第一个数为整个数组最小的值)。请自行百度什么是最小堆。
我们从数组S中取一个值,这个值和最小堆的第一个数first对比,有两种结果:
1,Si <= first 那么我们可以不做任何处理,因为我们找的是最大的K个数
2,Si > first 这时候,我们需要将Si的值赋值给最小堆的首元素。这时候我们可能会破坏最小堆的结构,所以需要重建最小堆。重建后,第一个元素同样是最小的。就这样一次比较下去。
不知道看没看明白,我们只需要一次将数组S中的元素与最小堆的首元素相比即可。
代码如下:
package com.xylx.utils.selectK; import com.xylx.utils.GsonUtils; /** * Created by on 17-5-10. */ public class MinHeap { public static void main(String[] args) { int[] arr = {2,6,3,9,4,8}; buildMinHeap(arr); System.out.println(GsonUtils.getJsonFromObject(arr)); change(5, arr); System.out.println(GsonUtils.getJsonFromObject(arr)); change(6, arr); System.out.println(GsonUtils.getJsonFromObject(arr)); } /** * 都建构建最小堆 * @param arr */ public static void buildMinHeap(int[] arr) { for (int i=0; i<arr.length; i++) { handleMinHeap(arr, arr.length, i); //从前向后构建最小堆 } } /** * 检查value是否比arr[0]大,如果大于,则对堆进行重构 * @param value * @param arr */ public static void change(int value, int[] arr) { if (value > arr[0]) { arr[0] = value; handleMinHeap(arr, arr.length, 0); } } /** * 最小堆排序 * @param arr */ public static void minHeapSort(int[] arr) { for (int i=arr.length-1; i>0; i--) { int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; handleMinHeap(arr, i, 0); } } /** * 节点转换 * @param arr * @param heapSize * @param current */ public static void handleMinHeap(int[] arr, int heapSize, int current) { int leftChildIndex = getLeftChiledIndex(current); int rightChildIndex = getRightChildIndex(current); int minIndex = current; //找到三个节点中最小的那个 if (leftChildIndex<heapSize && arr[current] > arr[leftChildIndex]) { minIndex = leftChildIndex; } if (rightChildIndex<heapSize && arr[minIndex] > arr[rightChildIndex]) { minIndex = rightChildIndex; } if (minIndex != current) { int tmp = arr[current]; //将最小的值转移到父节点位置,这样可能会破坏原先子节点的结果,所以需要递归 arr[current] = arr[minIndex]; arr[minIndex] = tmp; handleMinHeap(arr, heapSize, minIndex); } } /** * 获取左孩子节点位置 * @param current * @return */ public static int getLeftChiledIndex(int current) { return (current<<1) + 1; } /** * 获取右孩子节点位置 * @param current * @return */ public static int getRightChildIndex(int current) { return (current<<1) + 2; } /** * 获取父节点位置 * @param current * @return */ public static int getParentIndex(int current) { return (current-1)>>1; } }
说完了上面那个,让我们看看最终的大boss。
下面这个是面试中大部分面试官想要的结果。它的工作流程是:
得到数组中最大的值MAX,这样数组中所有的数都在[0, MAX]之间。申请一个数组countArr,大小为MAX+1。countArr每条记录是这个下标所代表的数组在数组中出现的次数。这样,我们只需要从前向后找到前K个最大的数即可。
代码如下:
package com.xylx.utils.selectK; /** * Created by zongjh on 17-5-8. */ public class FinalSelectK { public static void main(String[] args) { int[] arr = Constans.getLengthArr(100); Constans.printArr(arr); selectK(arr); } public static void selectK(int[] arr) { int max = getMax(arr); System.out.println("max="+max); int[] countArr = new int[max+1]; initArr(countArr); for (int i=0; i<arr.length; i++) { int index = arr[i]; countArr[index] = countArr[index] + 1; } int num = getK(countArr, max); System.out.println("num="+num); printK(arr, num); } private static void printK(int[] arr, int min) { int index = 0; System.out.println("最大的K个数:"); for (int i=0; i<arr.length; i++) { if (arr[i] > min) { System.out.print(arr[i]+" "); index++; } } for (int i=0; i<(Constans.K-index); i++) { System.out.print(min+" "); } } /** * 寻找第K大的数 * @param countArr * @param max * @return */ public static int getK(int[] countArr, int max) { int num = 0; int sumCount = 0; for (int i=max-1; i>0; i--) { sumCount += countArr[i]; if (sumCount >= Constans.K) { num = i; break; } } return num; } public static void initArr(int[] arr) { for (int i=0; i<arr.length; i++) { arr[i] = 0; } } /** * 得到数组最大值 * @param arr * @return */ public static int getMax(int[] arr) { if (arr == null || arr.length < 1) { return Integer.MIN_VALUE; } int max = Integer.MIN_VALUE; for (int i=0; i<arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } }
这里的辅助类,请参考之前的文章,看开头。
终于写完了,这个寻找最大的K个数。
最后一句话:说不定哪天你就用到这个面试题了!
其他技术文章:
DNS域名解析
CDN知道为什么是你?
靠谱的TCP:三次握手和四次挥手
喜欢聊技术或者聊观点,可以加入公号:【花生读书汇】
一起:励志,成长,学习,分享。
相关文章推荐
- 远程连接超出最大连接数的终极解决方案
- Windows 2003 远程桌面连接数超过最大连接数终极解决方案
- NYOJ-448 寻找最大数
- IDEA编译报无法确定 <T>T 的类型参数 ;对于上限为int,java.lang.Object 的类型变量 T,不存在唯一最大实例,解决方案
- JSP中文乱码问题终极解决方案
- 体验异步的终极解决方案-ES7的Async/Await
- 动态规划——寻找子矩阵最大和
- jsp url 参数加密传送的终极解决方案
- 创建解决方案时出错。已超过最大总文件大小限制(52428800 字节)。
- MySQL 乱码问题相关资料汇集 - 第三篇文章:MySQL4.1乱码终极解决方案
- WPF/Silverlight深度解决方案:(三)性能提升之终极攻略
- RecyclerView 平滑滚动可控制滚动速度 及 滚动的距离-终极解决方案
- Trapping Rain Water 左右指针寻找最大容量的水
- oracle 超出打开游标的最大数解决方案
- 寻找数组的子数组中和的最大值
- 编程之美 -- 寻找数组中的最大值和最小值
- 寻找一组数组中最大的一组子数组
- 寻找整数数组中的最大值
- Android大图片裁剪终极解决方案 原理分析