算法日记day 14(滑动窗口最大值)
创始人
2024-12-26 14:38:38
0

一、滑动窗口最大值

题目:

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置                最大值 ---------------               ----- [1  3  -1] -3  5  3  6  7       3  1 [3  -1  -3] 5  3  6  7       3  1  3 [-1  -3  5] 3  6  7       5  1  3  -1 [-3  5  3] 6  7       5  1  3  -1  -3 [5  3  6] 7       6  1  3  -1  -3  5 [3  6  7]      7 

示例 2:

输入:nums = [1], k = 1 输出:[1]

思路:

       采用大顶堆的方法,将滑动窗口所包含的值进行从大到小排序,最大的元素置于队列出口处,若新加入的元素大于入口处元素 ,则将入口的元素弹出,例如:窗口内元素为3,1 这时新加入的元素为2,2>1,因此弹出1,加入2,滑动窗口中的元素更新为3,2,保证队列队顶的元素始终为最大值

代码:

首先定义队列

class MyQueue {     Deque deque = new LinkedList<>();      // 添加元素时,保持队列单调递减的特性     void add(int val) {         while (!deque.isEmpty() && val > deque.getLast()) {             deque.removeLast();         }         deque.add(val);     }      // 弹出元素时,如果当前队列头部元素等于给定值,则弹出     void poll(int val) {         if (!deque.isEmpty() && val == deque.peek()) {             deque.poll();         }     }      // 返回队列头部元素(最大值)     int peek() {         return deque.peek();     } } 
  • add(int val): 添加元素时,保证队列中的元素是单调递减的。如果要添加的元素比队列末尾的元素大,就将末尾的元素弹出,直到满足单调递减的条件,然后将新元素加入队列末尾。

  • poll(int val): 弹出元素时,如果当前队列头部元素等于给定值 val,则将其弹出。这是为了确保移除的元素总是当前窗口中的元素。

  • 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();                  // 初始化第一个滑动窗口         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;     } } 
  • maxSlidingWindow(int[] nums, int k): 这个方法实现了找出数组 nums 中每个滑动窗口的最大值,并将结果存储在数组 res 中返回。

    • 首先判断特殊情况,如果数组长度为1,直接返回数组本身。
    • 计算结果数组 res 的长度 len,即为 nums.length - k + 1
    • 初始化自定义的 MyQueue 对象 myQueue,并将前 k 个元素依次加入队列中。
    • 第一个窗口的最大值通过 myQueue.peek() 获取并存储在 res 中。
    • 从第二个窗口开始遍历数组 nums,每次滑动窗口都先移除左边界元素(使用 myQueue.poll(nums[i - k])),然后加入右边界元素(使用 myQueue.add(nums[i])),再将当前窗口的最大值记录在 res 中。

 

二、前k个高频元素

题目:

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2] 

示例 2:

输入: nums = [1], k = 1 输出: [1]

思路:

遍历所有数中的元素出现的频率并记录,用小顶堆的方法,不断的去比较各元素出现的频率,始终保持堆中保存的是出现次数最大的那个元素

代码:

public int[] topKFrequent2(int[] nums, int k) {     Map map = new HashMap<>(); //key为数组元素值,val为对应出现次数     for (int num : nums) {         map.put(num, map.getOrDefault(num, 0) + 1);     }     //在优先队列中存储二元组(num, cnt),cnt表示元素值num在数组中的出现次数     //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)     PriorityQueue pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);     for (Map.Entry 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; }

 详细解释:

Map map = new HashMap<>(); for (int num : nums) {     map.put(num, map.getOrDefault(num, 0) + 1); }
  • 首先创建一个 HashMap 对象 map,用于统计每个元素出现的频率。遍历数组 nums,对于每个元素 num,使用 map.put(num, map.getOrDefault(num, 0) + 1) 来增加其频率计数。
PriorityQueue pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
  • PriorityQueue pq:创建一个优先队列,存储 int 数组,这些数组的结构为 {num, count},其中 num 是数组元素,count 是它的出现次数。
  • (pair1, pair2) -> pair1[1] - pair2[1]:这是一个比较器,指定了优先队列的排序方式。具体来说,它按照数组中的第二个元素(即出现次数 count)升序排列,这样堆顶元素始终是出现次数最小的。
for (Map.Entry entry : map.entrySet()) {
  • map.entrySet():遍历之前通过哈希映射统计得到的每个元素的频率信息。
if (pq.size() < k) {     pq.add(new int[]{entry.getKey(), entry.getValue()}); } else {     if (entry.getValue() > pq.peek()[1]) {         pq.poll();         pq.add(new int[]{entry.getKey(), entry.getValue()});     } } 
  • 如果队列未满 (pq.size() < k)

    • 将当前元素 entry.getKey()(元素值)和 entry.getValue()(出现次数)作为一个新的数组 {entry.getKey(), entry.getValue()} 加入优先队列 pq 中。
  • 如果队列已满

    • 检查当前元素的出现次数 entry.getValue() 是否大于堆顶元素的出现次数 pq.peek()[1]
    • 如果是,从堆顶移除最小的元素(即出现次数最少的),然后将当前元素的数组 {entry.getKey(), entry.getValue()} 加入堆中。这样可以保证堆中始终是出现次数最大的前 k 个元素。
int[] ans = new int[k]; for (int i = k - 1; i >= 0; i--) {     ans[i] = pq.poll()[0]; }
  • 创建一个大小为 k 的结果数组 ans,从小顶堆中依次弹出元素,将其存入结果数组中。由于小顶堆保证了每次弹出的元素是出现次数最大的前 k 个元素中的一个,因此这些元素按照频率从高到低排列在结果数组中。
return ans;
  • 最后返回结果数组 ans,其中包含了前 k 个高频元素按照频率降序排列的结果。

今天的学习就到这里了 

相关内容

热门资讯

第3分钟方针!潮汕掌上娱辅助科... 第3分钟方针!潮汕掌上娱辅助科技,火神辅助免费下载(辅助)总是是真的修改器(哔哩哔哩)1)潮汕掌上娱...
第8分钟办法!潮友潮汕木虱开挂... 第8分钟办法!潮友潮汕木虱开挂辅助器下载,新道游app辅助器(辅助)竟然是真的辅助(哔哩哔哩)1、起...
1分钟教程书!奇迹陕西挂,新道... 1分钟教程书!奇迹陕西挂,新道游app下载(辅助)一直真的有app(哔哩哔哩)1、很好的工具软件,可...
第8分钟烘培!wepoker修... 第8分钟烘培!wepoker修改工具,新二号透视辅助(辅助)其实存在有平台(哔哩哔哩)小薇(辅助器软...
9分钟项目!心悦透明器看手机纸... 9分钟项目!心悦透明器看手机纸牌,随意玩透视辅助软件(辅助)本来有挂修改器(哔哩哔哩)1、下载好心悦...
第八分钟法子!山西大唐辅助,来... 第八分钟法子!山西大唐辅助,来来拼十免费辅助(辅助)真是有挂脚本(哔哩哔哩)进入游戏-大厅左侧-新手...
第十分钟总结!星悦山东辅助,反... 第十分钟总结!星悦山东辅助,反杀大厅辅助(辅助)果然是真的辅助器(哔哩哔哩)运反杀大厅辅助辅助工具,...
两分钟要领!青龙辅助3.0,新... 两分钟要领!青龙辅助3.0,新星游辅助软件(辅助)原来是有app(哔哩哔哩)1、打开软件启动之后找到...
四分钟妙招!好友赣南辅助器,人... 四分钟妙招!好友赣南辅助器,人海大厅脚本(辅助)总是有挂插件(哔哩哔哩)1、实时好友赣南辅助器透视辅...
第八分钟指引!九酷众游辅助,新... 第八分钟指引!九酷众游辅助,新卡农辅助(辅助)一贯是真的辅助器(哔哩哔哩)1、进入游戏-大厅左侧-新...