今天是 Kevin 的算法之路的第2天。为大家讲解 LeetCode 第 215 题,是一道中等难度的题目。其中的 Top K 解法大厂很喜欢考查。
每日一笑
哥们几个逛街,看到电线杆子上有个重金求子的广告,我问旁边的马力:“这些是不是骗人的?”
他说:“算你聪明,他们会找你收体检费,看你的基因好不好,还有什么关系费,乱七八糟的费,加起来有6,7万。”
我不屑的说:“这么明显的骗局有人上当吗?”
他说:“肯定有啊,不然我怎么知道的那么清楚!”
题目描述
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 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 ≤ 数组的长度。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
方法一:暴力解法
根据题意,我们最容易想到先将数组进行排序,例如降序排序后,找第 K 个最大元素即找下标(index)为 K-1 的元素。千万不要忘了数组的下标是 0 开始哦 :)
当然升序排序也是可以的 ,找第 K 个最大元素即找下标(index)为 数组长度(length)- K 的元素。
这是最简单的思路,如果只回答这个方法,面试官肯定不满意。
但是在我们日常开发中,这种简单的方法反而不能忽视,因为:
- 简单的同时也容易编码,编码的成功率高,在进度快速的工作中先将问题解决是第一位的!面试做题也是如此!可以后期在持续优化
- 在数据规模小、对时间复杂度、空间复杂度要求不高的时候,简单问题简单做
- 先以简单的思路作出,可以为实现高级算法做铺垫,并且验证高级算法的正确性
虽然简单也给下代码(默认升序)
// Go
func findKthLargest(nums []int, k int) int {
sort.Ints(nums)
return nums[len(nums) - k]
}
// Java
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
Arrays.sort(nums);
return nums[len - k];
}
复杂度分析:
- 时间复杂度:O(NlogN),这里 N 是数组的长度,算法的性能消耗主要在排序,JDK 默认使用快速排序,因此时间复杂度为 O(NlogN)。
- 空间复杂度:O(1),这里是原地排序,没有借助额外的辅助空间。
方法二:基于堆的优先队列方法
对于第K个,前K个,我们应该条件反射的想到优先队列。
比如说此题的第K个最大元素,我们就可以维护一个长度为K的最小堆
注:优先队列的底层是堆结构,根据题目情况构建最大堆或者最小堆,简单的记就是:
如果是要前 K 个最大的元素,那就构造最小堆。
(逻辑就是,要前 K 个最大值时,如果待添加的元素大于堆中的最小值,就可以添加。 )如果是要前 K 个最小的元素,那就构造最大堆。
(逻辑就是,要前 K 个最小值时,如果待添加的元素小于堆中的最大值,就可以添加。 )
- 如果队列未满(size < k),直接添加
- 队列满时,如果读到的元素小于堆顶元素,不作处理;如果读到的元素大于堆顶元素,我们就将堆顶元素拿出,放入新元素
Tip:在很多语言中,都有优先队列或者堆的的容器可以直接使用,但是在面试中,面试官更倾向于让更面试者自己实现一个堆。所以建议读者掌握这里大根堆的实现方法,在这道题中尤其要搞懂「建堆」、「调整」和「删除」的过程。
如果不是很了解的朋友可以看看我之前写过的文章:
之前写过如何实现堆,这里就不在手写了,一下代码直接使用语言中自带的堆结构。
代码实现
// go
type TopList []int
func (t TopList) Len() int {
return len(t)
}
func (t TopList) Less(i, j int) bool {
return t[i] < t[j]
}
func (t TopList) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}
func (t *TopList) Push(x interface{}) {
*t = append(*t, x.(int))
}
func (t *TopList) Pop() interface{} {
x := (*t)[len(*t)-1]
*t = (*t)[:len(*t)-1]
return x
}
func findKthLargest(nums []int, k int) int {
m := make(TopList, 0)
size := 0
for i := range nums {
if size < k {
heap.Push(&m, nums[i])
size++
} else {
if m[0] < nums[i] { //小顶堆 堆顶元素小于当前元素
heap.Pop(&m)
heap.Push(&m, nums[i])
}
}
}
return m[0]
}
// java
public int findKthLargest(int[] nums, int k) {
int ans = 0;
int length = nums.length;
if (length == 0 || k == 0 || k > length){
return ans;
}
PriorityQueue<Integer> minHeap = new PriorityQueue<>(length, (o1, o2) -> o1.compareTo(o2));
for (int i = 0;i < length;i++){
if (minHeap.size() != k){
minHeap.offer(nums[i]);
}else if (minHeap.peek() < nums[i]) {
Integer tmp = minHeap.poll();
tmp = null; // GC
minHeap.offer(nums[i]);
}
}
return minHeap.remove();
}
郑重声明:
所展示代码已通过 LeetCode 运行通过,请放心食用~
在唠唠嗑
很多人都想养成好习惯,但大多数人却是三分钟热度。为了我们能一起坚持下去,决定制定如下计划(福利)
一起学算法,打卡领红包!
参与方式:
关注我的原创微信公众号「Kevin的学堂」,一起学习,一起更优秀!
打卡规则为:
「留言」“打卡XXX天” ➕「分享」到朋友圈
奖励:
连续打卡
21
天,联系本人获取6.6
元红包一个!连续打卡
52
天,联系本人获取16.6
元红包一个!连续打卡
100
天,联系本人获取66.6
元红包一个!