PS:《剑指offer》是很多同学找工作都会参考的一本面试指南,同时也是一本算法指南(为什么它这么受欢迎,主要应该是其提供了一个循序渐进的优化解法,这点我觉得十分友好)。现在很多互联网的算法面试题基本上可以在这里找到影子,为了以后方便参考与回顾,现将书中例题用Java实现(第二版),欢迎各位同学一起交流进步。
GitHub: https://github.com/Uplpw/SwordOffer。
剑指offer完整题目链接: https://blog.youkuaiyun.com/qq_41866626/article/details/120415258
1 题目描述
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
leetcode链接: 最小的k个数(以下代码已测试,提交通过)
2 测试用例
一般是考虑功能用例,特殊(边缘)用例或者是反例,无效测试用例这三种情况。甚至可以从测试用例寻找一些规律解决问题,同时也可以让我们的程序更加完整鲁棒。
(1)功能用例:数组正常,k>0。
(2)边缘用例:数组元素只有一个。
(3)无效用例:数组为空,k小于0或者k大于数组的长度。
3 思路
分析:
本题属于TopK一类的问题,像这种类型的问题一般都会有几种解法,下面分别介绍:
解法1:
利用快排:直接数组排序,然后取前k个元素。
时间空间复杂度: O ( n l o g n ) , O ( 1 ) O(nlogn),O(1) O(nlogn),O(1)
解法2:
利用快排的分区,取哨兵后进行数组交换,确定该哨兵在数据新的位置索引,并与k进行比较,(1)当索引等于k-1(因为是索引,所以需要减1),直接返回交换后数组的前k个元素;(2)当索引大于k-1,左边界不变,缩小右边界,继续递归;(3)当索引大于k-1,扩大左边界,右边界不变,继续递归。
时间空间复杂度: O ( n ) , O ( 1 ) O(n),O(1) O(n),O(1)
解法3:
维护一个大小为k的大顶堆:(堆顶元素是堆内最大值)
先将数组前k个元素添加到大顶堆,然后遍历后面的元素并与堆顶元素比较操作,当堆顶元素大于数组元素,则弹出堆顶元素把数组元素添加到堆;当堆顶元素小于数组元素,直接跳过。最后大顶堆保存的元素即为所求。
时间空间复杂度: O ( n l o g k ) , O ( k ) O(nlogk),O(k) O(nlogk),O(k)
4 代码
算法实现:
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
public class KLeastNumbers {
// 解法1:排序,取前面k个元素
public static int[] getLeastNumbers1(int[] arr, int k) {
if (arr == null || k <= 0 || arr.length < k) {
return new int[0];
}
Arrays.sort(arr);
int[] array = new int[k];
for (int i = 0; i < k; i++) {
array[i] = arr[i];
}
return array;
}
// 解法2:利用快排分区
public static int[] getLeastNumbers2(int[] arr, int k) {
if (arr == null || k <= 0 || arr.length < k) {
return new int[0];
}
return quickSort(arr, k, 0, arr.length - 1);
}
public static int[] quickSort(int[] arr, int k, int left, int right) {
int i = left, j = right;
while (i < j) {
while (i < j && arr[j] >= arr[left]) j--;
while (i < j && arr[i] <= arr[left]) i++;
swap(arr, i, j);
}
swap(arr, i, left);
// 当左边元素个数大于k,左边界不变,右边界缩小
if (i > k - 1) {
return quickSort(arr, k, left, i - 1);
}
// 当左边元素个数小于k,右边界不变,左边界增大
if (i < k - 1) {
return quickSort(arr, k, i + 1, right);
}
// 当左边元素等于k时,返回一个数组,等于数组arr的前k个元素
return Arrays.copyOf(arr, k);
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 解法3:最大堆
public static int[] getLeastNumbers3(int[] arr, int k) {
if (arr == null || k <= 0 || arr.length < k) {
return null;
}
int[] vec = new int[k];
// 维护一个大小为k的顺序堆
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
public int compare(Integer num1, Integer num2) {
return num2 - num1;
}
});
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上的官方、K神,以及其他的博客,在之后的每个例题详解后都会给出参考的思路或者代码链接,同学们都可以点进去看看!
本例题参考:
本文如有什么不足或不对的地方,欢迎大家批评指正,最后希望能和大家一起交流进步、拿到心仪的 offer !!!