题目
方法一-快速排序
算法思路:
排好第 k 小的数后,另外的 k - 1 个数刚好就是该数左边的部分。
快排算法
简单来说算法流程为:
假设左右边界分别为 i 和 j,a[i]为基准数
- j 从后向前移动找小于等于 pivot 的数,找到后赋给a[i],i++
- i 从前向后移动找大于 pivot 的数,找到后赋给 a[j],j–
- 重复上面两步直到 i == j,将 pivot 赋给 a[i]
具体代码:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0 || arr.length == 0)return new int[0];
return quickSort(arr, 0, arr.length - 1, k - 1);
}
//快速排序函数
private int[] quickSort(int[] nums, int left, int right, int k){
//切分一次
int i = partition(nums, left, right);
if(i == k){
return Arrays.copyOf(nums, k + 1);
}else{
return i > k ? quickSort(nums, left, i - 1, k) : quickSort(nums, i + 1, right, k);
}
}
//切分函数,返回基准数的位置索引
private int partition(int[] nums, int left, int right){
int pivot = nums[left];
int i = left, j = right;
while(i < j){
while(i < j && nums[j] > pivot)j--;//从右向左找第一个小于等于pivot的数
if(i < j)nums[i++] = nums[j];
while(i < j && nums[i] <= pivot)i++;//从左向右找第一个大于pivot的数
if(i < j)nums[j--] = nums[i];
}
nums[i] = pivot;
return i;
}
}
注意:
- 第 k 小的数,位置下标为 k - 1,故调用快排函数时最后一个参数为 k - 1而不是 k,即
quickSort(arr, 0, arr.length - 1, k - 1)
- 切分后,若
i == k
,返回的数组长度为 k,但这里的 k 实际上是 k - 1,故返回的数组长度应该为 k + 1 而不是 k,即Arrays.copyOf(nums, k + 1)
复杂度分析:
- 时间复杂度:期望为 O ( n ) O(n) O(n),n 为数组长度。因为递归时只需要处理一半,故时间复杂度是 n + n / 2 + n / 4 + . . . + n / n = 2 n n+n/2+n/4+...+n/n=2n n+n/2+n/4+...+n/n=2n。最坏情况下为 O ( n 2 ) O(n^2) O(n2),即,每次选择的 pivot 都是最大值或最小值,共划分 n - 1 次,每次划分的时间复杂度为 O ( n ) O(n) O(n),则一共需要 O ( n 2 ) O(n^2) O(n2) 的时间。
- 空间复杂度:期望为 O ( l o g n ) O(logn) O(logn),递归调用的平均深度为 O ( l o g n ) O(logn) O(logn),每层需要 O ( 1 ) O(1) O(1) 的空间。最坏情况下为 O ( n ) O(n) O(n),递归调用最深 n - 1 层。
方法二-堆
算法思路:
建一个大根堆,遍历数组,
- 若当前堆的大小小于 k ,直接放入堆中
- 否则判断当前数字 num 与堆顶元素 x 的大小关系:
1.若num < x
,将堆顶元素出堆,再放入 num;
2.若num >= x
,继续向后遍历
具体代码:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0 || arr.length == 0)return new int[0];
//创建容量为k的大根堆,由于默认是小根堆,需要重写比较器
Queue<Integer> maxHeap = new PriorityQueue<Integer>(k, new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2){
return o2.compareTo(o1);
}
});
for(int num : arr){
if(maxHeap.size() < k){
maxHeap.offer(num);//若当前堆的大小小于 k ,直接放入堆中
}else if(num < maxHeap.peek()){//否则若当前数字小于堆顶元素
maxHeap.poll();//删除堆顶元素
maxHeap.offer(num);//放入当前元素
}
}
//返回堆中的元素
int[] res = new int[maxHeap.size()];
int i = 0;
for(int num : maxHeap)res[i++] = num;
return res;
}
}
复杂度分析:
- 时间复杂度: O ( n l o g k ) O(nlogk) O(nlogk),n 为数组长度。因为堆的大小为 k,所以插入删除的时间开销均为 O ( l o g k ) O(logk) O(logk),最坏情况下数组里 n 个数都会插入,所以共需要 O ( n l o g k ) O(nlogk) O(nlogk) 的时间复杂度
- 空间复杂度: O ( k ) O(k) O(k),堆的大小为 k。
方法三-排序
算法思路:
将数组元素递增排序,则排序后前 k 个数就是最小的 k 个数。
具体代码:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] res = new int[k];
Arrays.sort(arr);
for(int i = 0; i < k; i++){
res[i] = arr[i];
}
return res;
}
}
复杂度分析:
- 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),n 为数组长度,因为排序算法的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
- 空间复杂度: O ( l o g n ) O(logn) O(logn),排序所需的额外空间为 O ( l o g n ) O(logn) O(logn)