- 时间复杂度为O(k+(n-k)*logk)= O(N logK);
- k个元素的最大堆的空间复杂度为O(k)
- 初始化K个数的大顶堆
- 对K+1等之后的数字要进行更新大顶堆的操作,由于大顶堆表示顶部的元素是最大的,所以如果后面的数小于大顶堆最大值才能有资格更新大顶堆(即删除大顶堆中最大的元素),从而使得大顶堆的顶部元素变小;如果大于大顶堆中的最大值也就是顶部元素,说明该元素没有资格成为数组中最小值的候选人。
- 整个数组遍历完之后将大顶堆中的每个元素都存储到最终的输出结果。
需要注意的点:
- if语句中判断范围的放在前面,后面再进行数组索引 eg: ✅if(right<lengthh&&heap[left]<heap[right]) ❌ if(heap[left]<heap[right]&&right<lengthh)
- 索引的时候一定要看准再写,不然后面调试真的一时半会儿找不出来问题 eg:易错点if(arr[i]<heap[0]){ heap[0]=arr[i]; ...别写成if(arr[k]<heap[0]){ heap[0]=arr[k]; ...
- 注意{}的含义是代码块。
在代码块内侧定义的变量,作用域范围在代码块。这点与C不同,所以java里面可以在循环体内不断定义同名变量,但为了养成良好的习惯,尽量在循环体外定义,在循环体内初始化数值。
- 1、选择第一个元素作为比对数
- 2、i指针从前往后遍历,j指针从后往前遍历
- 3、如果i没有到数组的末尾而且索引i处的数值大于比对数,则i不动去遍历j
- 4、如果j没有到数组的开头而且索引j处的数值小于比对数,则将索引i与j两处所指向的数值进行交换,接下来继续回到步骤3不懂移动i和j
- 5、当i>=j则终止循环,将此时j处的数值与比对数进行位置变换,注意此时j处的数值一定小于等于比对数,因为i=j时j处的数值与比对数相同,交换没有关系,i>j时,此时两个指针一定是相互交错的,遍历的一定是已经处理完的数值(也就是前部分都小于等于比对数,后部分都大于等于比对数)。
- 6、然后分别对前部分和后部分递归调用快排算法。
- 核心思想是如果左侧刚好为K个,则一次part就是结果
- 左侧不够K个则对右边排序选m-k个补充到结果
- 左侧大于K个则对左侧进一步排序直到左侧为K个。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
// 最后一个参数表示我们要找的是下标为k-1的数
return quickSearch(arr, 0, arr.length - 1, k - 1);
}
private int[] quickSearch(int[] nums, int lo, int hi, int k) {
// 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
int j = partition(nums, lo, hi);
if (j == k) {
return Arrays.copyOf(nums, j + 1);
}
// 否则根据下标j与k的大小关系来决定继续切分左段还是右段。
return j > k? quickSearch(nums, lo, j - 1, k): quickSearch(nums, j + 1, hi, k);
}
// 快排切分,返回下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边。
private int partition(int[] nums, int lo, int hi) {
int v = nums[lo];
int i = lo, j = hi + 1;
while (true) {
while (++i <= hi && nums[i] < v);
while (--j >= lo && nums[j] > v);
if (i >= j) {
break;
}
int t = nums[j];
nums[j] = nums[i];
nums[i] = t;
}
nums[lo] = nums[j];
nums[j] = v;
return j;
}
}
代码截图: