剑指 Offer 40. 最小的k个数
*215. 数组中的第K个最大元素
*347. 前 K 个高频元素
输入整数数组 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
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k==0 || arr==null){
return new int[0];
}
if(arr.length <= k){
return arr;
}
Arrays.sort(arr);
int[] ans = new int[k];
for(int i=0; i<k; i++){
ans[i] = arr[i];
}
return ans;
}
}
如果数的上限不是很大可以这么做
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
int[] count = new int[10001];
for (int i: arr) {
count[i]++;
}
int[] res = new int[k];
int idx = 0;
for (int i = 0; i < count.length; i++) {
while (count[i]-- > 0 && idx < k) {
res[idx] = i;
idx++;
}
if (idx == k) {
break;
}
}
return res;
}
}
可以使用Java 中提供了现成的优先队列 PriorityQueue,或者自己手动建堆
用一个容量为 K 的大根堆,每次 poll 出最大的数,那堆中保留的就是前 K 小
保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0 || arr.length == 0) return new int[]{
};
// 默认是小根堆,实现大根堆需要重写一下比较器
Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
for(int num : arr) {
if(pq.size() < k) {
pq.offer(num);
} else if(num < pq.peek()) {
pq.poll();
pq.offer(num);
}
}
int[] res = new int[k];
for(int i = 0; i < k; i++) {
res[i] = pq.poll();
}
// for(int num: pq) {
// res[idx++] = num;
// }
return res;
}
}
找前 K 大/前 K 小问题不需要对整个数组进行 O(NlogN) 的排序,直接通过快排切分排好第 K 小的数(下标为 K-1),那么它左边的数就是比它小的另外 K-1 个数。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0 || arr.length == 0) return new int[]{
};
if(arr.length <= k) return arr;
return quickSearch(arr, 0, arr.length - 1, k);
}
// 快排
public int[] quickSearch(int[] nums, int left, int right, int k) {
int j = partition(nums, left, right);
// 这次划分刚好下标是k,那说明从头到j位置刚好是要找的最小的k个元素
if(j == k) {
return Arrays.copyOf(nums, j);
}
// 根据j的大小确定继续细分左半部分或者右半部分
return j > k ? quickSearch(nums, left, j - 1, k) : quickSearch(nums, j + 1, right, k);
}
// 划分,返回下标j,是的比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边
public int partition(int[] nums, int left, int right) {
int pivot = nums[left]; // 选择区间的最左端元素作为基准
while(left < right) {
while(left < right && nums[right] >= pivot) right--;
nums[left] = nums[right];
while(left < right && nums[left] <= pivot) left++;
nums[right] = nums[left];
}
nums[left] = pivot;
return left;
}
}