目录
描述
给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。
数据范围:0≤k,n≤100000≤k,n≤10000,数组中每个数的大小0≤val≤10000≤val≤1000
要求:空间复杂度 O(n)O(n) ,时间复杂度 O(nlogn)O(nlogn)
示例1
输入:
[4,5,1,6,2,7,3,8],4
复制返回值:
[1,2,3,4]
复制说明:
返回最小的4个数即可,返回[1,3,2,4]也可以
示例2
输入:
[1],0
复制返回值:
[]
示例3
输入:
[0,1,2,1,2],3
复制返回值:
[0,1,1]
复制
思想:快速排序、堆排序-优先级队列、手写大顶堆
代码
import java.util.ArrayList;
public class Solution {
//快速排序
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> arr = new ArrayList<>();
int len = input.length;
// if(k == 0 || k == len) return input.toList();
if(k < len){
quickSort(input, k, 0, len-1);
}
for(int i = 0; i < k; i++){
arr.add(input[i]);
}
return arr;
}
public void quickSort(int[] input, int k, int a, int b){
if(a >= b) return;
int i = a;
int j = b;
int temp = input[a];
while(i < j){
while(i < j && input[j] >= temp) j--;
while(i < j && input[i] <= temp) i++;
swap(input, i, j);
}
swap(input, i, a);
if(i == k-1){
return;
}
quickSort(input, k, a, i-1);
quickSort(input, k, i+1, b);
}
public void swap(int[] input, int a, int b){
int temp = input[a];
input[a] = input[b];
input[b] = temp;
}
//大顶堆 - 优先级队列
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> res = new ArrayList<>(k);
//根据题意要求,如果K>数组的长度,返回一个空的数组
if (k > input.length || k == 0)
return res;
//创建最大堆
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>(){
public int compare(Integer i1, Integer i2) {
//降序
return i2.compareTo(i1);
}
});
//先在堆中放数组的前k个元素
for (int i = 0; i < k; ++i) {
queue.offer(input[i]);
}
//因为是最大堆,也就是堆顶的元素是堆中最大的,遍历数组后面元素的时候,
//如果当前元素比堆顶元素大,就把堆顶元素给移除,然后再把当前元素放到堆中,
for (int i = k; i < input.length; ++i) {
if (queue.peek() > input[i]) {
queue.poll();
queue.offer(input[i]);
}
}
//最后再把堆中元素转化为数组
for (int i = 0; i < k; ++i) {
res.add(queue.poll());
}
return res;
}
//手写大顶堆
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> list = new ArrayList<Integer>();
// [4,5,1,6,2,7,3,8],0
if (input == null || k > input.length || k <= 0)
return list;
int[] target = new int[k];
int len = input.length;
for (int i = 0; i < len; ++i) {
if (i < k) {
target[i] = input[i];
heapInsertSiftUp(target, i, target[i]);
} else {
if (target[0] > input[i]) { // 最大堆下沉
target[0] = input[i];
siftDown(target, 0, target[0]);
// 相比优先级队列,这里不会offer操作(里面有上浮),少了一步上浮调整,效率高了不止一丁点
}
}
}
for (int i = 0; i < k; ++i) {
list.add(target[i]);
}
return list;
}
private void heapInsertSiftUp(int[] target, int index, int x) {
while (index > 0) {
int parent = (index - 1) >>> 1;
if (greater(x, target[parent])) {
target[index] = target[parent]; // 往下拉,避免直接上浮覆盖前面的值
index = parent;
} else {
break;
}
}
target[index] = x;
}
private boolean greater(int i, int j) {
return i > j;
}
private void siftDown(int[] target, int k, int x) {
int half = target.length >>> 1;
while (k < half) {
int child = (k << 1) + 1; // 默认先左孩子
int big = target[child];
int right = child + 1;
if (right < target.length && greater(target[right], big)) {
big = target[right];
child = right; // 可以直接一步big = target[child = right];
}
if (greater(x, big)) // x比子节点中的最大值还大,已经是大顶堆了
break; // 往上拉不动了,准备退出把最初堆顶的结点赋值到上一个结点
target[k] = big; // 往上拉
k = child;
}
target[k] = x;
}
}
}