算法day12 | 队列和栈:队列的应用(滑动窗口、前k数频率)

1. 滑动窗口问题:利用双端队列实现自定义单调队列

虽然是叫滑动窗口,但和双指针的滑动窗口含义不太一样,双指针滑动窗口方法长度往往可变(最小覆盖子串等),这里的窗口长度不可变,因此更类似于队列

1.1 链接

https://leetcode.cn/problems/sliding-window-maximum/

1.2 关键知识点

  • 暴力方法,遍历一遍的过程中每次从窗口中再找到最大的数值,这样很明显是O(n × k)的算法
  • 这个进入滑动窗口的过程,类似队列,
  • Deque(double ended queue简称,习惯上称之为双端队列)
    • 既可以作为Stack使用(Last-In-First-Out,push\pop\peek在开头)
    • 又可以作为Queue使用(First-In-First-Out)

使用实现Deque接口的类有ArrayDeque、LinkedList。如果要实现队列或双端队列数据结构,则ArrayDeque可能比LinkedList快


ArrayDeque<Type> animal = new ArrayDeque<>();
//如果使用Deque<String>声明队列,你可以更轻松地将队列实现切换为另一个实现类,而不必更改代码中的所有引用。这种做法可以使代码更加灵活和可维护。
Deque<Type> animal = new ArrayDeque<>();

1.3 自己遇到的细节问题

  • 对栈java中是双端队列实现的这一点要理解
  • 使用没有定义输入类型的类,应该是CustomQueue rsQueue = new CustomQueue();,没有<>
  • 读取的方法使用前(如getLast和peek,都要有为空的判断)
  • 为什么滑动窗口最大值不用优先级队列?该题不用排序窗口内所有的,只用把可能大的值加入队列,和队列口的元素比较就可以了

1.4 题解

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        CustomQueue rsQueue = new CustomQueue();
        int m = nums.length;
        int[] rs = new int[m - k  + 1];
        for(int i = 0;i < m; i ++){
            rsQueue.add(nums[i]);//滑入
            if(i >= k -1){//遍历满足k个就可以开始获取max
                rs[i - k + 1] = rsQueue.peek();
                rsQueue.poll(nums[i - k + 1]);//滑出
            }
        }
        return rs;
    }
}

class CustomQueue{
    Deque<Integer> customQueue;
    public CustomQueue(){
        customQueue = new ArrayDeque<>();
    }
    void add(int x){
        //如果不小于队列尾部,就加入到尾部。如果小于,移除尾部
        while((!customQueue.isEmpty()) && x > customQueue.getLast()){
            customQueue.removeLast();
        }
        customQueue.add(x);
    }
    //如果队列头部等于要加入的值就弹出
    void poll(int x){
        if((!customQueue.isEmpty()) && customQueue.peek() == x){
            customQueue.poll();
        }
    }

    int peek(){
        return customQueue.peek();
    }
}

2.前K个大数问题:优先级队列实现小顶堆

2.1 链接

2.2 关键知识点

  • map计算频率(虽然很简单但自己总是不能很快写出来)
  Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
  for(int num:nums){
      map.put(num,map.getOrDefault(num,0)+1);
  }
  • 如何创建一个小顶堆(min-heap)
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
//其中的元素是k-v二维数组
PriorityQueue<int[]> pq = new PriorityQueue<>((p1,p2) -> p1[1] - p2[1]);
  • 遍历map Entry的kv的方法
//如果只是import java.util.*  注意迭代map 需要 for(Map.Entry<Integer,Integer> a :map.entrySet())
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
}

//或者通过entry的get(key),遍历key获取k-v
for (Integer i : Sites.keySet()) {
    System.out.println("key: " + i + " value: " + Sites.get(i));
}

2.3 自己遇到的细节问题

  • entry获得键值是 getKey(),getValue()

2.4 题解

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //1把频率读取map中
        Map<Integer, Integer> map = new HashMap<>();
        for(int i : nums){
            map.put(i, map.getOrDefault(i,0) + 1); //注意拼写
        }

        //2创建小顶堆(规则)
        PriorityQueue<int[]> pq = new PriorityQueue<>((p1, p2) -> p1[1] - p2[1]);
        
        //3遍历map
        for(Map.Entry<Integer, Integer> entry : map.entrySet()){ //注意Map.Entry<yaoxieleixing>
            //如果小等于k,直接add入队列/小顶堆(logk的排序)
            if(pq.size() < k){ //要求为k个,应该是< k,最后一个add完就是k了
                pq.add(new int[]{entry.getKey(),entry.getValue()});
            }else{
            //如果大于k
                //如果大于堆顶
                if(entry.getValue() > pq.peek()[1]){ //注意peek出来的结果是二维的
                    //poll堆顶后add入队列y二维数组 .add(new int[]{})
                    pq.poll();
                    pq.add(new int[]{entry.getKey(),entry.getValue()});
                }
            }
        }

        //4输出小顶堆,此时堆顶为第k高,堆底为最高,顺着反向输出都行。每输出一个poll一个 
        int[] rs = new int[k];
        for(int i = k -1;i >= 0;i --){//注意是等于0
            rs[i] = pq.poll()[0]; //只需要key
        }
        return rs;
    }
}

3.总结

3.1 基本知识

常用方法

双端队列和栈都可以用Deque实现。java.util.Deque双端队列来实现队列与栈的各种需求

  • pop() 一般指弹栈
  • poll()指出队
    两者都是删除双端队列第一个元素:
    pop() 方法等价于removeFirst(), 底层调用的是removeFirst()方法。
    poll()方法等价于pollFirst() 方法,底层调用的是pollFirst()。

栈:
加入到双端队列开头:push
弹出双端队列开头pop/poll
返回双端队列开头:peek

双端队列:
加入双端队列最后面:add
弹出双端队列最后面:removeLast
读取双端队列最后面:getLast

弹出、读取双端队列前面的方法和栈比较类似
在这里插入图片描述
判断空均为:isEmpty

3.2 基础知识

栈里面的元素在内存中是连续分布的么?
这个问题有两个陷阱:

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

具体来说,Java中的Deque接口有两个标准实现:ArrayDeque和LinkedList。其中,ArrayDeque是使用循环数组实现的,而LinkedList是使用链表实现的。在这两个实现中,只有ArrayDeque是使用连续的内存块来存储元素的。而LinkedList则是由一系列节点(Node)通过指针相连来实现的,因此其内存不是连续的。
需要注意的是,虽然ArrayDeque使用了连续的内存块来存储元素,但是其实现方式并不是将所有元素存储在一个连续的内存块中。相反,它将元素分散存储在多个连续的内存块中,并使用循环数组的方式来实现Deque的操作。

在Java中:

Queue提供了迭代器来遍历它们的元素。

		Queue<String> queue = new LinkedList<String>();
        for (String q : queue) {
            System.out.println(q);
        }

        Iterator it = queue.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        while (!queue.isEmpty()) {
            System.out.println(queue.poll());
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值