LC215 解决看似简单其实其妙无穷的topN问题|刷题打卡

499 阅读1分钟

本系列使用IDEA+LEETCODE EDITOR插件,题目描述统一英文题目链接
一、题目描述:

//Given an integer array nums and an integer k, return the kth largest element i
//n the array. 
//
// Note that it is the kth largest element in the sorted order, not the kth dist
//inct element. 
//
// 
// Example 1: 
// Input: nums = [3,2,1,5,6,4], k = 2
//Output: 5
// Example 2: 
// Input: nums = [3,2,3,1,2,4,5,5,6], k = 4
//Output: 4
// 
// 
// Constraints: 
//
// 
// 1 <= k <= nums.length <= 104 
// -104 <= nums[i] <= 104 

二、思路分析:
这道题先用暴力解解完我哑然一笑,怎么这时间复杂度还行呢?一细看题目描述,原来限制了参数的数量最多只有区区104个。所以暴力解大家看看就好,还是不能用于生产环境的。TOPN问题是非常常见的问题,比如你打游戏时候的战力排行榜,比如用户的积分排名等等,这种时候的入参就可能多答几百万,到了面试场景下可能要你算的是XX GB的数据中前10怎么取了,还会限制你内存大小。不被题目描述的场景所局限而是考虑到实际的应用场景,这个才是这期的重点内容

三、AC 代码:
首先展示下暴力版,可以AC,但是生产肯定是不能用的。

class Solution {
    public int findKthLargest(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length - k];
    }
}
	解答成功:
			执行耗时:2 ms,击败了90.29% 的Java用户
			内存消耗:38.8 MB,击败了43.37% 的Java用户

这里聊一下Arrays.sort(),一个排序接口,排序我们知道性能稳定的是快排,但是在一些场景下其他排序算法会比快排性能更好,而这个接口就封装了几种排序,根据传入的参数进行判断选择合适的几种排序算法进行组合。详情可以看这篇文章 ,摘录结尾部分如下:

  数量非常小的情况下(就像上面说到的,少于47的),插入排序等可能会比快速排序更快。 所以数组少于47的会进入插入排序。

  快排数据越无序越快(加入随机化后基本不会退化),平均常数最小,不需要额外空间,不稳定排序。

  归排速度稳定,常数比快排略大,需要额外空间,稳定排序。

  所以大于或等于47或少于286会进入快排,而在大于或等于286后,会有个小动作:“// Check if the array is nearly sorted”。这里第一个作用是先梳理一下数据方便后续的归并排序,第二个作用就是即便大于286,但在降序组太多的时候(被判断为没有结构的数据,The array is not highly structured,use Quicksort instead of merge sort.),要转回快速排序。

回到本题,要解决TOPN问题,生产可以使用中间件如REDIS的SORTED SET结构存储数据,感兴趣的自行了解即可。 在JAVA中,通常使用大/小顶堆解决TOPN问题,而JAVE也封装了一个专门的数据结构:PriorityQueue。源码并不多,不到千行,主要就是封装了构造堆的基本操作比如上浮(加入时,加入到最后,上浮构造成堆),下沉(删除时把最后一位跟堆顶交换,删除最后一位后堆顶要下沉重新构造成堆)。 思路:定义K大小的小顶堆优先级队列,也就是最小的数在堆顶(这里有点绕),如果优先级队列没满则加入,否则比对堆顶和新元素,新元素大就弹出堆顶,加入新元素,否则忽略。 比如堆里目前 9、8、7 三个数,7在堆顶,此时10过来了,比对7和10,弹出7,加入10,此时8在堆顶。 使用了优先级队列/堆的AC代码:

   public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(k);
        for (int num : nums) {
            if(priorityQueue.size() < k){
                priorityQueue.add(num);
            }else if(priorityQueue.peek() < num){
                priorityQueue.poll();
                priorityQueue.add(num);
            }
        }
        return priorityQueue.poll();
    }
				解答成功:
			执行耗时:4 ms,击败了59.14% 的Java用户
			内存消耗:38.8 MB,击败了60.30% 的Java用户

四、总结:

  1. 题目的场景和实际使用的场景可能差距非常大,比如这道题,在没有要求的情况下应该主动思考和询问面试官,这道题的要求是答出来即可还是有实际的使用场景,不然写出不能用代码就真的是纸上谈兵了。
  2. 了解Arrays.sort()内部的原理。
  3. 了解堆排序的原理和JAVA对堆的封装PriorityQueue的使用。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情