Leetcoder Day11|栈与队列part03(队列的应用)

发布时间:2024年01月20日

语言:Java/C++?

239.?滑动窗口最大值?

给定一个数组 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

本题的难点在于,如何每次控制一个元素的进出以及在当前k个元素里寻找最大值。我们需要一个队列,在队列中放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是什么。因此需要自己设置一个这样的队列。

队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。

那么如何取到窗口内最大元素呢?如果要push的元素大于队尾元素的值,那么就将队尾的元素弹出,直到push元素的值小于等于队列入口元素的值为止。这样就能保证队列的第一个元素即为最大元素。

如何移动窗口呢?若当前队列的第一个元素也就是最大值,为遍历到的当前元素,则pop队列中的这个元素,否则不用进行其他操作。

因此,单调队列的定义和构造如下:

class MyQueue{
     Deque<Integer> deque = new LinkedList<>();

     void push(int val){
         while(!deque.isEmpty() && val>deque.peekLast()){//当前元素值大于队尾值,则移除队尾元素
             deque.pollLast();
         }
         deque.offer(val);
     }

     void pop(int val){
         if(!deque.isEmpty() && val==deque.peek()){
             deque.pollFirst();
         }
     }

     int peek(){
         return deque.peekFirst();
     }
}

在定义单调队列的时候犯了一个很低级的错误,将int peek() 定义为了void,void返回的是方法,int返回数值,这里我们需要获取队首元素的值,因此应该是int。后面的代码如下:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length==1) return nums;
        int len=nums.length-k+1; //若原属数组是单调递增的,则最多能存放size-k+1个结果
        MyQueue queue= new MyQueue();
        int[] res = new int[len];       //存放结果元素的数组
        int count=0;//设置一个存放结果的计数器
        //先将前k的元素放入队列,实际上是一个只保存最大值及其后面比他小的值的单调递减序列
        for(int i=0;i<k;i++){
            queue.push(nums[i]);
        }
        res[count++]=queue.peek(); //将当前最大值存入
        //进行滑动
        for(int i=k;i<nums.length;i++){
            queue.pop(nums[i-k]);
            queue.push(nums[i]);
            res[count++]=queue.peek();
        }
        return res;
    }
}

347.前?K?个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

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

示例 2:

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

提示:

  • 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
  • 你的算法的时间复杂度必须优于 O(n \log n)?, n 是数组的大小。
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
  • 你可以按任意顺序返回答案

对于这道题,首先想的就是定义一个大小为size-k的map,然后用暴力解法将每个出现过的数字的频率统计一遍,但是这样太麻烦了。这里就涉及了一个新的队列类型:优先级队列

优先级队列

优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。而内部元素是自动依照元素的权值排列的。

缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。?如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

本题不用快排的原因:快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。

本题不用大顶堆的原因:定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,不好保留前K个高频元素。

所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        //将出现的次数和值存入map中,值为key,次数为value
        for(int num:nums){
            map.put(num,map.getOrDefault(num,0)+1);
        }
        //创建一个小顶堆,比较规则是按照数组中元素的第二个值(出现次数)升序排列
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]); 
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){//遍历map
            if(pq.size()<k){ //小顶堆只需要维持k个元素有序,若小顶堆元素个数小于k个,全部放入
            pq.add(new int[]{entry.getKey(),entry.getValue()});
            }else{
                if(pq.peek()[1]<entry.getValue()){//若当前元素大于小顶堆的根节点
                    pq.poll();
                    pq.add(new int[]{entry.getKey(), entry.getValue()});
                }
            }
        }
        int[] res=new int[k];
        //依次弹出小顶堆的结果并返回,先弹出的是堆顶,次数最少
        for(int i=k-1;i>=0;i--){
            res[i]=pq.poll()[0];
        }
        return res;


    }
}

栈与队列总结篇

面试题:栈里面的元素在内存中是连续分布的么?

这个问题有两个陷阱:

  • 陷阱1:栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中不一定是连续分布的。
  • 陷阱2:缺省情况下,默认底层容器是deque,那么deque在内存中的数据分布是什么样的呢? 答案是:不连续的,下文也会提到deque。

栈经典题目

对称匹配问题,大部分需要用到栈结构

linux系统中,cd这个进入目录的命令:

cd a/b/c/../../

系统是如何知道进入了a目录呢 ,这就是栈的应用。这在leetcode上也是一道题目,编号:71. 简化路径

递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。

队列的经典题目

要注意多种队列的类型:单调队列,优先队列。。。

文章来源:https://blog.csdn.net/weixin_36675391/article/details/135648391
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。