一、题目描述
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
二、解答
1.直接调API
直接向面试官展示调API的熟练度…
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
Arrays.sort(arr);
int[] res = new int[k];
for(int i = 0;i<k;i++){
res[i] = arr[i];
}
return res;
}
}
2.堆
分析
- 使用大根堆 维护当前最小的 k 个数,此时堆顶是当前k个数最大的,接下来 数组剩下的元素 需要和 当前堆顶元素比较即可
- 大的话,直接丢弃
- 小的话,插入堆的最底层最后位置(保证是完全二叉树),然后从底向上进行堆化,即理解为末尾总是最小的值。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] vec = new int[k];
if (k == 0) { // 排除 0 的情况
return vec;
}
// 优先队列实现堆,传入 Comparator 可以决定是大根堆,小根堆
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
public int compare(Integer num1, Integer num2) {
return num2 - num1;
}
});
// 先对数组前k的元素建堆
for (int i = 0; i < k; ++i) {
queue.offer(arr[i]);
}
// 出现比 大根堆 的 堆顶 元素更小的值,需要插入到 优先队列尾,重新进行堆化
for (int i = k; i < arr.length; ++i) {
if (queue.peek() > arr[i]) {
queue.poll();
queue.offer(arr[i]);
}
}
// 结果
for (int i = 0; i < k; ++i) {
vec[i] = queue.poll();
}
return vec;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/zui-xiao-de-kge-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.快排
分析
- 关于快排可以看这个视频:https://www.bilibili.com/video/BV1at411T75o?from=search&seid=5725303854576012858&spm_id_from=333.337.0.0
- 快排关于中间轴的划分可以是随机的,也可以采用默认的最左边 作为随机轴,右边值同理
随机选取随机值作为轴
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
// 快排
randomizedSelected(arr, 0, arr.length - 1, k);
int[] vec = new int[k];
for (int i = 0; i < k; ++i) {
vec[i] = arr[i];
}
return vec;
}
private void randomizedSelected(int[] arr, int l, int r, int k) {
if (l >= r) {
return;
}
// 随机划分
int pos = randomizedPartition(arr, l, r);
int num = pos - l + 1;
// 最后一次快排的中间轴 左边的元素正是结果
if (k == num) {
return;
} else if (k < num) {
randomizedSelected(arr, l, pos - 1, k);
} else {
randomizedSelected(arr, pos + 1, r, k - num);
}
}
// 基于随机的划分
private int randomizedPartition(int[] nums, int l, int r) {
// 随机选中间值,选完之后跟 右边界 交换
int i = new Random().nextInt(r - l + 1) + l;
swap(nums, r, i);
// 快排
return partition(nums, l, r);
}
// 快排
private int partition(int[] nums, int l, int r) {
int pivot = nums[r];
int i = l - 1;
for (int j = l; j <= r - 1; ++j) {
if (nums[j] <= pivot) {
i = i + 1;
swap(nums, i, j);
}
}
swap(nums, i + 1, r);
return i + 1;
}
// 交换
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/zui-xiao-de-kge-shu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
或者 默认选择 快排区间的最左边值作为中心轴
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);
// 经过若干次 刚好 中间值为 k
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;
}
}
或者 默认选择快排区间 的最右边界值作为 中间轴
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0) return new int[k];
find(arr, 0, arr.length-1, k);
int result[] = new int[k];
for(int i = 0; i < k; i++) {
result[i] = arr[i];
}
return result;
}
public void find(int[] arr, int l, int r, int k) {
// 快排中间轴
int pos = partition(arr, l, r);
if(pos+1 == k) return;
if(pos+1 > k) {
find(arr, l, pos-1, k);
} else {
find(arr, pos+1, r, k);
}
}
public int partition(int[] arr, int l, int r) {
int p = arr[r];
int i = l-1;
for(int j = l; j < r; j++) {
// 右边的出现小值,需要换到左边
if(arr[j] <= p) {
// 左边界
i++;
swap(arr, i, j);
}
}
swap(arr, i+1, r);
return i+1;
}
public void swap(int[] arr, int a, int b) {
int t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
}