代码随想录算法训练营第十二天 | LeetCode 239. 滑动窗口最大值、LeetCode 347. 前 K 个高频元素
文章链接:代码随想录滑动窗口最大值 代码随想录前K个高频元素
视频链接:代码随想录滑动窗口最大值 代码随想录前K个高频元素
1. LeetCode 239. 滑动窗口最大值
1.1 思路
- 题目的意思有点类似一个队长为k的队列里求最大值,每次队列往后移一位,每次都求最大值,因此可以实现一个单调队列,poll()方法每次移动的时候就抛弃不要的元素,add()方法每次移动的时候就添加队尾新元素,peek()方法每次移动完就求最大值,这个数据结构需要自己实现,我们管这个叫单调队列
- 由于是自定义的数据结构,所以不用每次都把k个元素放入队列中,只需要维护队首即为最大值即可,下面以“1,3,-1,-3,5,3,2,1”举例
- poll()方法就是遗弃不需要的元素,add()方法就是添加新的元素,peek()就是求最大值弹出队首
- 最开始1先add()进来,然后3add()进来,这时判断如果队尾有元素比3大就弹出,直到没有比3大的为止,意思就是左边的元素如果比新加入的元素小就可以全部弹出(又老又小),add()进来的元素比前面都大前面的元素就都要排除
- poll()方法则是如果要移除的元素正是队首元素(最大值),则弹出,否则不用操作
- 注意此题中我们自己实现的时候,其实很多弹出操作在add()方法里就实现了。并且注意,删除的时候(不管是add还是poll方法)要在前面加条件 !deque.isEmpty() 判断队列不为空,不然就对空队列操作了
1.2 代码
//自定义数组 class MyQueue { Deque<Integer> deque = new LinkedList<>(); //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出 //同时判断队列当前是否为空 void poll(int val) { if (!deque.isEmpty() && val == deque.peek()) { deque.poll(); } } //添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出 //保证队列元素单调递减 //比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2 void add(int val) { while (!deque.isEmpty() && val > deque.getLast()) { deque.removeLast(); } deque.add(val); } //队列队顶元素始终为最大值 int peek() { return deque.peek(); } } class Solution { public int[] maxSlidingWindow(int[] nums, int k) { if (nums.length == 1) { return nums; } int len = nums.length - k + 1; //存放结果元素的数组 int[] res = new int[len]; int num = 0; //自定义队列 MyQueue myQueue = new MyQueue(); //先将前k的元素放入队列 for (int i = 0; i < k; i++) { myQueue.add(nums[i]); } res[num++] = myQueue.peek(); for (int i = k; i < nums.length; i++) { //滑动窗口移除最前面的元素,移除是判断该元素是否放入队列 myQueue.poll(nums[i - k]); //滑动窗口加入最后面的元素 myQueue.add(nums[i]); //记录对应的最大值 res[num++] = myQueue.peek(); } return res; } }
2. LeetCode 347. 前 K 个高频元素
2.1 思路
- 难点1:如何求对应元素的出现频率。难点2:如何把频率排序,并求前k个
- 对于难点1,可以用map数据结构,因为是key-value,key存放对应的元素,value就是频率
- 对于难点2,就维护k个高频元素的一个有序的集合,就用到了大根堆或者小根堆,这个数据结构很适合求很大数据集里的前k个高频或者低频的结果
- 如何用堆求前k个高频元素呢?只需要用堆来遍历map里边的所有元素,然后堆里维持k个元素,然后遍历完后堆里的k个元素就题目所求。问题来了用大根堆还是小根堆呢?其实应该用小根堆的,因为用大根堆的话,因为堆pop元素时只能从堆顶弹出,用大根堆的话就把最大的弹出了,因此不行。那么用小根堆把小的都弹出,最后剩下k个元素就是要求的了
2.2 代码
//解法2:基于小顶堆实现 public int[] topKFrequent2(int[] nums, int k) { Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数 for(int num:nums){ map.put(num,map.getOrDefault(num,0)+1); } //在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数 //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆) PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]); for(Map.Entry<Integer,Integer> entry:map.entrySet()){//小顶堆只需要维持k个元素有序 if(pq.size()<k){//小顶堆元素个数小于k个时直接加 pq.add(new int[]{entry.getKey(),entry.getValue()}); }else{ if(entry.getValue()>pq.peek()[1]){//当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个) pq.poll();//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了 pq.add(new int[]{entry.getKey(),entry.getValue()}); } } } int[] ans = new int[k]; for(int i=k-1;i>=0;i--){//依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多 ans[i] = pq.poll()[0]; } return ans; }