【算法】大厂常喜欢问的算法题

今天是 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 个最小值时,如果待添加的元素小于堆中的最大值,就可以添加。 )

  1. 如果队列未满(size < k),直接添加
  2. 队列满时,如果读到的元素小于堆顶元素,不作处理;如果读到的元素大于堆顶元素,我们就将堆顶元素拿出,放入新元素

Tip:在很多语言中,都有优先队列或者堆的的容器可以直接使用,但是在面试中,面试官更倾向于让更面试者自己实现一个堆。所以建议读者掌握这里大根堆的实现方法,在这道题中尤其要搞懂「建堆」、「调整」和「删除」的过程。

如果不是很了解的朋友可以看看我之前写过的文章:

《算法撕裂者》系列0 - TopK问题

《算法撕裂者》01-手把手实现二叉堆

之前写过如何实现堆,这里就不在手写了,一下代码直接使用语言中自带的堆结构。

代码实现

// 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 元红包一个!

长按扫码,一起进步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值