方法一:k轮取最值(部分选择排序)
适合k非常小的情况;
import java.util.ArrayList; public class Solution { public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { ArrayList<Integer> res = new ArrayList<Integer>(); if(k<=0 || k>input.length)return res; for(int i=0; i<=k-1; ++i){ int min = Integer.MAX_VALUE; int index = 0;//标记这一轮的min的位置 for(int j=0; j<=input.length-1; ++j){ if(input[j] < min){ min = input[j]; index = j; } } res.add(min); input[index] = Integer.MAX_VALUE;//将此轮min给移除 } return res; } } //时间复杂度:O(N*k) //空间复杂度:O(k)
方法二:快排Partition思想
优点:平均时间O(N)
缺点:会在原数组上修改;输出的k个值无序; 最坏时间O(N^2)
import java.util.ArrayList; public class Solution { public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { ArrayList<Integer> res = new ArrayList<Integer>(); if(k<=0 || k>input.length)return res; int divide = 0; int left =0; int right =input.length-1; while(true){ divide = partition(input, left, right); if(divide > k-1){ right=divide-1; } else if(divide < k-1){ left = divide+1; } else if(divide == k-1)break; //到位(不能是k, k会溢出) } for(int i=0; i<=k-1; ++i){ res.add(input[i]); } return res; } public int partition(int [] input, int left, int right){//【设计递归函数的原则:尽量精简】 int temp = input[left]; while(left<right){ while(left<right && input[right]>=temp)--right;//注意【快排的大小判断 有等号】 input[left] = input[right];//覆盖的时候,不需要移动下标 while(left<right && input[left]<=temp)++left; input[right] = input[left]; } input[left] = temp; return left;//此时left==right } } //时间复杂度分析:partition会遍历left~right; //平均情况下:O(N)+O(N/2)+...+O(1)=O(2N)=O(N) //最坏情况下:O(N)+O(N-1)+...+O(1)=O(N*(N-1)/2)=O(N^2) //空间:max{O(logN),O(k)} //1.快排栈深度是logN,每层空间O(1) 2.res占用空间
方法二延伸:非递归的partition方法
- 树状多分叉递归=》可转化为栈辅助非递归
- 线性递归=》可转化为:外加一层while循环;
(要注意的是:递归使用的形参,如果外层也使用,则需要备份保存)import java.util.ArrayList; public class Solution { public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { ArrayList<Integer> res = new ArrayList<Integer>(); if(k > input.length || k<=0) return res; int left = 0; int right = input.length-1; while(left<right){ int l = left;//保留left值 int r = right; int temp = input[l]; int divide =-1;//divide的作用是增加可读性 while(l<r){ while(l<r && input[r]>=temp)--r;//再次强调,快排中的等号不能少 input[l] = input[r]; while(l<r && input[l]<=temp)++l; input[r] = input[l]; } input[l] = temp; divide = l; if(divide == k-1)break; else if(divide > k-1){ right = divide-1; } else if(divide < k-1){ left = divide+1; } } for(int i=0; i<=k-1; ++i){ res.add(input[i]); } return res; } }
方法三:堆排序
——适合处理海量流数据 时间O(N*logK) 空间O(k)
//堆排序适合海量流数据,因为在运算的时候,允许后去不断输入、而不会破坏前面已经计算的结果 //使用Java的PriorityQueue基于堆;堆的规模是k,而不是N //add/remove/poll操作的时间复杂度都是O(logK) ,peek复杂度是O(1) //top-k问题:求最大用小根堆;求最小用大根堆。 import java.util.ArrayList; import java.util.PriorityQueue; import java.util.Collections; public class Solution { public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { ArrayList<Integer> res = new ArrayList<Integer>(); if(k<=0 || input.length<k)return res;//【思路:先分再总】==>先分别考虑k和input,然后考虑之间关系 //得到:if(k<=0 || input.length==0 || input.length < k),然后将后两项合并 PriorityQueue<Integer> maxRootHeap = new PriorityQueue<Integer>(k,Collections.reverseOrder());//堆初始化的时候必须要有参数(堆大小:大根/小根,默认小根堆) for(int i=0; i<=input.length-1; ++i){ if(maxRootHeap.size()<k)//这里不能等于k maxRootHeap.add(input[i]);//开始时候直接入堆 else if(maxRootHeap.peek()>input[i]){//大根堆的peek是堆中的最大值,如果input[i]比堆中最大值还大,就直接忽略 maxRootHeap.poll(); maxRootHeap.add(input[i]); } } for(int i=0; i<=k-1; ++i){ res.add(maxRootHeap.poll()); } return res; } } //时间复杂度O(Nlogk) //空间复杂度O(k)==>大根堆的大小