剑指offer——最小的K个数
来源:互联网 发布:苹果软件开发培训 编辑:程序博客网 时间:2024/05/12 13:14
1. 问题描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
2.问题解答
这相当于部分排序,想法也有可能是多种多样,基本上是在查找与排序上进行一些优化。如何让两个子任务获得平衡的衔接,是要思考的问题。
2.1 level1
最简单的办法就是查找K次,每次查找最小的那个数加入到最终结果中,这样的时间复杂度是O(KN),这还是使用了hash表的情况。
//查找K次,时间复杂度是O(KN)
public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { ArrayList result = new ArrayList(); if(k>input.length) return result; Set<Integer> indexs = new LinkedHashSet(); for (int i = 0; i < k; ++i) { int min = Integer.MAX_VALUE; int minIndex=-1; for (int j = 0; j < input.length; ++j) { if (min > input[j]) { if (!indexs.contains(j)) { minIndex=j; min = input[minIndex]; } } } if(minIndex==-1) break; indexs.add(minIndex); } for (int index : indexs) { result.add(input[index]); } return result; }
2.2 level2
当然,我们会想运用类似动态规划的思想来做,那就是遍历一次数组,每经历一个数字,就去判断是否应当把当前数字添加进去。理想状态的话,时间复杂度应当是O(N),但是由于我们过分理想化判断是否应当把当前数字添加进去。而下面的版本实现中,实现了O(K)的时间复杂度,因此最终的时间复杂度是O(KN)。
public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { ArrayList<Integer> result=new ArrayList<Integer>(); if(input.length<=0||k<=0||k>input.length){ return result; } for(int i=0;i<input.length;i++){ result=Insert(result,input[i],k); } return result; } public static ArrayList<Integer> Insert(ArrayList<Integer> res,int aim,int k){ int i=0; for(;i<res.size()&&i<k;i++){ //如果有比它大的数 if(res.get(i)>=aim){ res.add(i, aim); //如果已经增加过度了,就移除多的那个。 if(res.size()>k) res.remove(k); return res; } } //当还不够n的时候 if(i<k) res.add(aim); return res; }
当然,已经可以证明,查找的最低时间复杂度为O(logn),因此这种方法最低也只能达到O(nlogk)。
2.3 level3
当然,如果想省事的话,可以直接使用快速排序,这样的话,时间复杂度能达到O(nlogn),比之前的两个O(KN)要小上一些。
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { Arrays.sort(input);//快速排序 ArrayList<Integer> list=new ArrayList<Integer>(); if(k>input.length|k<=0){//判断是否存在越界 return list; }else{for (int i = 0; i < k; i++) { list.add(input[i]); } return list; } }
2.4 level4
如果提到了快排,当然不能少了它的著名优化partition方法,它经过优化后,最快可以达到O(N)的时间复杂度。
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) { ArrayList<Integer> res = new ArrayList<>(); if (input == null || input.length < k) return res; quickSolve(input, 0, input.length - 1, k); for (int i = 0; i < k; i++) { res.add(input[i]); } return res; } public void quickSolve(int[] arr, int left, int right, int k) { if (left > right) { return; } int id = partition(arr, left, right); if (id == k) { return; } if (id > k) { quickSolve(arr, left, id - 1, k); } else { quickSolve(arr, id + 1, right, k); } } public int partition(int[] arr, int left, int right) { int i = left + 1; int j = right; while (true) { while (j >= left + 1 && arr[j] > arr[left]) { j--; } while (i <= right && arr[i] < arr[left]) { i++; } if (i > j) break; swap(arr, i, j); i++; j--; } swap(arr, left, j); return j; } public void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
2.5 level5
如果使用最大堆的话,时间复杂度就可以达到理想的O(NlogK)了。
// 使用最大堆 public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { ArrayList<Integer> list = new ArrayList<>(); if(k <= 0 || k > input.length){ return list; } // 将数组input的前k的元素构造成堆 for(int i = (k - 1) / 2; i >= 0; i --){ down(input, i, k); } for(int i = k; i < input.length; i++){ if(input[i] >= input[0]){ continue; } // 该元素小于堆顶元素 input[0] = input[i]; down(input, 0, k); } for(int i = 0;i < k; i++){ list.add(input[i]); } Collections.sort(list); return list; } // 下滤操作,k表示堆的大小,hole表示需要下滤的元素的位置 public void down(int[] a, int hole, int k){ // 左孩子和右孩子位置 int left = 2 * hole + 1; int right = 2 * hole + 2; if(left >= k){ // 左孩子位置大于等于k,则超出堆的大小,即该结点没有孩子了 return; }else if(right >= k){ // 只有左孩子 if(a[hole] < a[left]){ swap(a, hole, left); } return; }else{ // 左右孩子都有 if(a[hole] > a[left] && a[hole] > a[right]){ // 比两个孩子的值都大,则下滤结束 return; }else if(a[left] > a[right]){ // 不是比两个孩子都大,那么如果左孩子大于右孩子,则下滤到左孩子的位置 swap(a, hole, left); // 对左孩子进行递归下滤 down(a, left, k); }else{ // 下滤到右孩子 swap(a, hole, right); // 对右孩子进行递归下滤 down(a, right, k); } } } // 交换两个元素 public void swap(int[] a, int i, int j){ int tmp = a[i]; a[i] = a[j]; a[j] = tmp; }
当然看起来可能很复杂,但是使用Java自带的优先队列来构建堆,就会容易很多了,这里,我们构建一个小顶堆(堆顶最小)。
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { ArrayList<Integer> list = new ArrayList<>(); if(input == null || input.length == 0 || k > input.length) return list; PriorityQueue<Integer> minPQ = new PriorityQueue<>(); for(int i = 0; i < input.length; i++) { minPQ.offer(input[i]); } for(int i = 0; i < k; i++) { list.add(minPQ.poll()); } return list; }
3. 小结
上面的前2种方法,都已经知道,要把这个任务分为2个部分,一个部分是外循环,另一个是内循环,外循环是固定的,必须达到O(N),而在内循环部分,尚有优化空间,Level5就是对前两种的优化。而Level3和4则是从另外一个角度,先进行排序,后进行查找,这样的话,就可以把两个子任务合二为一,通过最优的算法,可以实现时间复杂度为O(N)的算法,这也不失为一种明智的选择。
- 剑指offer—最小的K个数
- 剑指offer — 最小的k个数
- 《剑指offer》——最小的k个数
- 剑指offer——最小的K个数
- 剑指Offer—— 最小的K个数
- 剑指Offer——(29)最小的k个数
- 剑指offer——最小的K个数
- 剑指Offer—29—最小的 K 个数
- 剑指offer——29.最小的k个数
- 剑指offer 面试题30—最小的k个数
- 剑指offer(29)—最小的K个数
- 剑指offer:最小的k个数
- 【剑指offer】最小的k个数
- 剑指offer-30:最小的k个数
- 最小的k个数(剑指offer)
- 剑指offer--最小的K个数
- 剑指Offer之 - 最小的k个数
- 剑指offer-30 最小的K个数
- 找合法帧
- Redis
- Linux文件系统编程 系统调用 打开读取写入文件
- 1.读取excel文件,将输入存储到数据库中(JXL) 2.完成商品的检索相关功能 1.根据分类,显示分类下所有的商品信息,按照库存量从低到高排序(提供补货依据) 2.模糊搜索,根据商品信息(名
- javascript学习回顾
- 剑指offer——最小的K个数
- 百度之星2017资格赛T3 度度熊与邪恶大魔王 背包
- mac os 下Nginx+PHP环境配置
- 【2017.08.06】
- 问题 I: 约瑟夫问题
- c/c++中define用法详解及代码示例
- FFT IP核的简明使用教程
- 链式队列的创建
- D_D系统构建-Loader的保护模式尝试