栈与队列

185 阅读6分钟

用栈实现队列

题目:232

  • 队列:先进先出 栈:先进后出。所以如果只用一个栈来模拟队列,是不太行的,一个不行那就来两个

  • 一个用来进,一个用来出

  • 队列要实现的作用无非是:

  • push(将元素x推至队列尾部):直接用栈的push就行

  • pop(从队列的开头移除并返回元素):因为栈是先进后出,要想实现队列的先进先出,得用另一个栈起到转运元素的作用。就先把In栈里的元素放到Out栈里的元素,这样Out栈的第一个元素就是曾经先进去的元素了。然后就用Out栈的pop

  • peek(返回队列开头元素):和pop一样的思路

  • empty(检查队列是否为空):两个栈空了就是空了

  • 细节上来说:构造器里就是两个栈,然后由于push和pop都要用到两个栈用来转移元素,为了代码的复用性,直接弄一个方法用来转移元素,要转移就是转移全部的。看Out栈是非空的,原路返回,看In栈是非空的,都放到Out栈里

  • 以及很尴尬......我忘了怎么定义栈了

  • Stack<Integer> stackIn; Stack<Integer> stackOut;

    public MyQueue() {

      stackIn = new Stack<>();
      
      stackOut = new Stack<>();
    

    }

用队列实现栈

题目:225

  • 或许是一种把思路逆转过来

  • ......好像并没有,队列好像有点不熟。不过也是可以用两个队列,一个是和栈的进出顺序一样的q1,一个是用来辅助的q2。因为栈是先进后出,后push的是先出来的,但是队列后push的是在后面,也是后出,为了避免这个情况,可以先把要push的元素x 放在辅助队列p2,这样她就是第一个元素可以先出来了,然后再其后面挨个放p1的元素。这样p2就和栈的顺序一样了,但是为了保证p1 p2分工明确,这里要把p1 p2交换一下

  • 震惊,queue的定义竟然是

  • 
      Queue<Integer> queue1; // 和栈中保持一样元素的队列
    
      Queue<Integer> queue2; // 辅助队列
    
      /** Initialize your data structure here. */
      public MyStack() {
          queue1 = new LinkedList<>();
          queue2 = new LinkedList<>();
      }
    
    
  • deque的定义更是没怎么用过

  • Deque 接口继承了 Queue 接口

  • 所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst

  •   Deque<Integer> que1; // 和栈中保持一样元素的队列
      Deque<Integer> que2; // 辅助队列
      /** Initialize your data structure here. */
      public MyStack() {
          que1 = new ArrayDeque<>();
          que2 = new ArrayDeque<>();
      }
    
    
  • 当然,还可以优化,只用一个队列,靠一些首尾循环。 每 offer 一个数(A)进来,都重新排列,把这个数(A)放到队列的队首 `

      public void push(int x) {
    
      queue.offer(x);
      int size = queue.size();
      //移动除了 A 的其它数
      while (size-- > 1)
          queue.offer(queue.poll());
      }
    

有效的括号

题目:20

  • 经典题目,找括号配对,看是否配对成功
  • 字符串里有个左括号,就往栈里放一个配对的右括号。然后等轮到字符串里不是左括号时,与栈的顶部做对比(此时栈的顶部是最近的右括号)直接用pop ,对不上就返回false
  • 最后如果顺利的话,栈里是没有元素的,说明配对成功
  • 主要是栈的操作,代码能力还是堪忧x

删除字符串中所有相邻重复项

题目:1047

  • 简单来说,就是继续用栈的特性,对比字符串里的字符ch 与 目前在栈的顶端元素,如果两者一样,把顶端元素pop出去。一些连连看x 最后剩下来的就是符合要求的了
  • 不过因为在栈里,出来后的顺序会反,不过可以自行调整拼接顺序。
while (!deque.isEmpty()) {
            str = deque.pop() + str;  //注意看,拼接时是把栈顶的元素放在了前面,相当于一种反转顺序
        }
  • 感觉有必要看一下双端队列Deque的使用
  • 普通队列(一端进另一端出): Queue queue = new LinkedList()或Deque deque = new LinkedList()
  • 双端队列(两端都可进出) Deque deque = new LinkedList()
  • 堆栈 Deque deque = new LinkedList()
  • 而在堆栈这里,都什么年代了,还在用传统Stack 好像现在JAVA更倾向于用Deque作为堆栈......,不过方法一样,都是push pop peek
  • 顺便,上面三个看起来好像都是一个东西......这个双端队列Deque有点兼容,想让它是什么,就用对应的方法就行。具体在csdn上也收藏了一个文章
  • 这题甚至能用双指针,等着去试试

逆波兰表达式求值

题目:150

  • 题目的提示就可以立大功了
  • 逆波兰表达式主要有以下两个优点:
  1. 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  2. 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
  • 按照提示2,进行分类操作,检测到不同运算符就进行不同操作,把两个数字取出运算然后再压入栈内。是数字就直接压入栈内

滑动窗口最大值

题目:239

  • 其中心思路好像是:利用一个单调队列,让它的队列头结点始终是当前窗口的最大值
  • 在这里就维护好看见的最大值就行,如果后来进入的值比目前的队尾数值大,那就把后进入的最大值的前面的值poll出去,一直到队列为空(该值最大) 或者 前面有大于该值的数(此时进入的值算是以后窗口最大值的预备役)
  • 除了值的判定外,也要注意节点位置的判定,要在窗口范围内
  • 然后,从第一个窗口里的值判定完后,一直取队列里的队首最大值即可
  • 大概思路可能就是这样,代码记得多看看

前K个高频元素

题目:347

  • 首先要解决一下,题解中出现的什么大顶堆、小顶堆,优先级队列之类的。要不代码都看不懂......
  • 遍历Map for (Map.Entry<Integer,Integer> entry:map.entrySet())
  • 获取Key:entry.getKey() 获取Value:entry,getValue()
  • getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。
  • 大顶堆:

PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->pair2[1]-pair1[1]);

  • 小顶堆:从小到大排
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
  • 然后把元素add到优先队列级后,优先队列会把它们按照自定义的排序方式排序。靠一些智能数据结构。
  • 总之,大顶堆就取前k个。小顶堆先把次数少的几个poll掉,后面剩的都是所要的k个元素了,再挨个取
  • 依然是多看代码吧