题目描述:
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
参考解题思路一:排序,对原数组从小到大排序,取前k个数
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] vec = new int[k];
Arrays.sort(arr);
for (int i = 0; i < k; ++i) {
vec[i] = arr[i];
}
return vec;
}
}
复杂度分析
时间复杂度:O(nlogn),其中 n是数组 arr 的长度。算法的时间复杂度即排序的时间复杂度。
空间复杂度:O(logn),排序所需额外的空间复杂度为 O(logn)。
参考解题思路二:大根堆
用一个大根堆实时维护数组的前 k 小值。首先将前 k个数插入大根堆中,随后从第k+1 个数开始遍历,如果当前遍历到的数比大根堆的堆顶的数要小,就把堆顶的数弹出,再插入当前遍历到的数。最后将大根堆里的数存入数组返回即可。
public int[] getLeastNumbers(int[] arr, int k) {
int[] array = new int[k];
// 边界值判断,k为0时
if (k == 0) {
return array;
}
// 大根堆
PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
for (int i = 0 ; i < k; i++) {
queue.offer(arr[i]);
}
for (int i = k; i < arr.length; i++) {
if (arr[i] < queue.peek()) {
queue.poll(); // 取出堆顶元素
queue.offer(arr[i]); // 重新放入新元素
}
}
for (int i = 0; i < k; i++) {
array[i] = queue.poll();
}
return array;
}
复杂度分析
时间复杂度:O(nlogk),其中 n是数组 arr 的长度。由于大根堆实时维护前 k小值,所以插入删除都是O(logk) 的时间复杂度,最坏情况下数组里 n个数都会插入,所以一共需要 O(nlogk) 的时间复杂度。
空间复杂度:O(k),因为大根堆里最多 k个数。
参考解题思路三:快速排序
快速排序原理:快速排序算法有两个核心点,分别为 “哨兵划分” 和 “递归” 。
- 哨兵划分操作: 以数组某个元素(一般选取首元素)为 基准数 ,将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。
- 递归: 对 左子数组 和 右子数组 递归执行 哨兵划分,直至子数组长度为 1 时终止递归,即可完成对整个数组的排序。
public int[] getLeastNumbers3(int[] arr, int k) {
if (k == 0) {
return arr;
}
return quickSort(arr, k, 0, arr.length-1);
}
public int[] quickSort(int[] arr, int k, int l, int r) {
int i = l;
int j = r;
while (i < j) {
// 最右边的值比目标值arr[l]都大
while (i < j && arr[j] >= arr[l]) {
j--;
}
// 最左边的值都比目标值小
while (i < j && arr[i] <= arr[l]) {
i++;
}
// 找到左右需要互换的值
swap(arr, i, j);
}
// 交换哨兵值
swap(arr, i, l);
// 前k小的数值小于找到的i--递归左子数组执行哨兵划分
if (i > k) {
return quickSort(arr, k, l, i-1);
}
// 前k小的数值大于找到的i--递归右子数组执行哨兵划分
if (i < k) {
return quickSort(arr, k, i+1, r);
}
// 等于k时--截取数组前K个数字大小
return Arrays.copyOf(arr, k);
}
public void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
复杂度分析:
时间复杂度 O(NlogN) : 库函数、快排等排序算法的平均时间复杂度为 O(NlogN) 。
空间复杂度 O(N) : 快速排序的递归深度最好(平均)为 O(logN) ,最差情况(即输入数组完全倒序)为O(N)。
作者:jyd
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/jian-zhi-offer-40-zui-xiao-de-k-ge-shu-j-9yze/