方法一: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)==>大根堆的大小 
京公网安备 11010502036488号