这是我参与更文挑战的第 9 天,活动详情查看: 更文挑战
最小 K 个数(面试题17.14)
题目描述
设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。
示例 1:
输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
提示
0 <= len(arr) <= 100000
0 <= k <= min(100000, len(arr))
思路分析
想到找到最小 K 个数,最简单粗暴的方法可以对数组进行排序,然后直接找到最小 K 个数,比如我们用快速排序,时间复杂度是,但是我们可以利用快排的思想进一步优化,快排利用的是分治的思想,取一个值,大于该值的在左侧,小于该值的在右侧。找到最小 K 个数,可以转化为先找到第(length - K)大的数。
每次快排后,我们就可以缩小我们的区间,因为我们不必对数组进行排序,只需要找到最小的一部分的值,所以最终的时间复杂度是。
代码展示
解法一:时间复杂度是,空间复杂度是。
public int[] smallestK(int[] arr, int k) {
if (arr.length == 0){
return arr;
}
int m = arr.length - k;
int start = quickSort(arr,m);
int[] nums = new int[k];
int index = 0;
for (int i = m;i < arr.length;i++){
nums[index++] = arr[i];
}
return nums;
}
private int quickSort(int[] nums,int k){
return quickInternal(nums,0,nums.length - 1,k);
}
private int quickInternal(int[] nums,int left,int right,int k){ //5,4,3,2,2,2,1,0
int division = division(nums, left, right);
if (division + 1 == k){
return division;
} else if(division + 1 > k){
return quickInternal(nums,left,division - 1,k);
} else {
return quickInternal(nums,division + 1,right,k);
}
}
private int division(int[] nums,int left,int right){
int base = nums[left];
while (left < right){
while (left < right && nums[right] <= base){
right--;
}
nums[left] = nums[right];
while (left < right && nums[left] > base){
left++;
}
nums[right] = nums[left];
}
nums[left] = base;
return left;
}
数组中的第 K 个最大元素(215)
题目描述
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
进阶:
- 你可以在
O(n log n)
时间复杂度和常数级空间复杂度下,对链表进行排序吗?
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
思路分析
这道题和上面的题目很类似,想到找到数组中的第 K 个最大元素,最简单粗暴的方法可以对数组进行排序,然后直接找到第 K 个最大元素,比如我们用快速排序,时间复杂度是,但是我们可以利用快排的思想进一步优化,快排利用的是分治的思想,取一个值,大于该值的在左侧,小于该值的在右侧。
每次快排后,我们就可以缩小我们的区间,因为我们不必对数组进行排序,只需要找到最小的一部分的值,所以最终的时间复杂度是。
代码展示
解法一:时间复杂度是,空间复杂度是。
public int findKthLargest(int[] nums, int k) {
return quickSort(nums,k);
}
private int quickSort(int[] nums,int k){ //3,2,1
return quickInternal(nums, 0, nums.length - 1,k);
}
private int quickInternal(int[] nums,int left,int right,int k){
int division = division(nums,left,right); //5,4,3,2,1 5
if (division+1 == k){
return nums[division];
} else if (division+1 > k){
return quickInternal(nums,left,division-1,k);
} else {
return quickInternal(nums,division+1,right,k);
}
}
private int division(int[] nums,int left,int right){
int base = nums[left];
while (left < right){
while (left < right && nums[right] <= base){
right--;
}
nums[left] = nums[right];
while (left < right && nums[left] > base){
left++;
}
nums[right] = nums[left];
}
nums[left] = base;
return left;
}
总结
对于这类的第 K 大/小元素,最大/小 K 个数,解题思路都是一样的,利用快排思想,省去了具体的详细排序,时间复杂度是,空间复杂度是。