算法训练--栈与队列

436 阅读12分钟

算法训练--栈与队列

基础概念

  • 队列是先进先出,栈是先进后出

java中栈的实现

  • 有两个方法:一个是用java本身的集合类型Stack类型;另一个是借用LinkedList来间接实现Stack

  • stack的实现常用api如下:

    boolean               isEmpty() // 判断当前栈是否为空
    synchronized E        peek() //获得当前栈顶元素
    synchronized E        pop() //获得当前栈顶元素并删除
                 E        push(E object) //将元素加入栈顶
    synchronized int      search(Object o)  //查找元素在栈中的位置,由栈低向栈顶方向数
    
  • LinkedList的实现

    LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList 实现 List 接口,能对它进行队列操作。LinkedList 实现 Deque 接口,即也能将LinkedList当作双端队列使用,LinkedList被当做栈使用时的常用api如下:

    栈方法        等效方法
    push(e)      addFirst(e)
    pop()        removeFirst()
    peek()       peekFirst()     
    isEmpty()  //判断是否为空
    
  • 单调栈

    单调增还是单调减取决于出栈顺序:如果出栈的元素是单调增的,那就是单调递增栈,如果出栈的元素是单调减的,那就是单调递减栈

    [1,2,3,4] 就是一个单调递减栈(因为此时的出栈顺序是 4,3,2,1)

java中队列的实现

  • java中虽然有Queue接口,单java并没有给出具体的队列实现类,而Java中让LinkedList类实现了Queue接口,所以使用队列的时候,一般采用LinkedList。因为LinkedList是双向链表,可以很方便的实现队列的所有功能。

    Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素使用poll()来获取并移出元素。它们的优点是通过返回值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常。 如果要使用而不移出该元素,使用element()或者peek()方法

    使用LinkedList实现的队列,常用的api如下:

    Queue<E> queue = new LinkedList<E>();
    
    队列方法       等效方法
    offer(e)      offer(e)/offerLast(e)  //进队列,将元素加入队列末尾
    poll()        poll()/pollFirst()  //获取队列头的元素并移除
    peek()        peek()/peekFirst()  //获取队列头的元素    
    isEmpty() //判断是否为空
    
  • 简单示例

    Deque<Integer> deque=new LinkedList<>();
    deque.offer(1);
    deque.offer(2);
    deque.offer(3);
    System.out.println(deque.peek());    		---  1
    System.out.println(deque.peekLast());		---	 3  pollLast()
    System.out.println(deque.peekFirst());	---  1	pollFirst()
    

相关题目练习

232. 用栈实现队列

  • 题目描述

    image.png

  • 题解

    232.用栈实现队列版本2

    class MyQueue {
      	//in负责进栈 out负责出栈
        Stack<Integer> stackIn;
        Stack<Integer> stackOut;
        public MyQueue() {
            stackIn=new Stack<>();
            stackOut=new Stack<>();
        }
        
        public void push(int x) {
            stackIn.push(x);
        }
        
        public int pop() {
            dumpStackIn();
            return stackOut.pop();
        }
        
        public int peek() {
            dumpStackIn();
            return stackOut.peek();
        }
        
        public boolean empty() {
            return stackIn.isEmpty() && stackOut.isEmpty();
        }
      	//如果stackOut为空,那么将stackIn中的元素全部放到stackOut中
        public void dumpStackIn(){
            if(!stackOut.isEmpty()) return;
            while(!stackIn.isEmpty()){
                stackOut.push(stackIn.pop());
            }
        }
    }
    
    /**
     * Your MyQueue object will be instantiated and called as such:
     * MyQueue obj = new MyQueue();
     * obj.push(x);
     * int param_2 = obj.pop();
     * int param_3 = obj.peek();
     * boolean param_4 = obj.empty();
     */
    

225. 用队列实现栈

  • 题目描述

    image.png

  • 题解

    225.用队列实现栈

    /**
    	可以使用两个队列
    	但可以用使用一个双端队列来进行优化
    */
    class MyStack {
        Deque<Integer> deque;
        public MyStack() {
            deque=new LinkedList<>();
        }
        
        public void push(int x) {
            deque.addLast(x);
        }
        
        public int pop() {
            int size=deque.size();
            //预留最后一个元素
            size--;
            while(size-- >0){
                //留下最后一个值,其余拷贝到队尾
                deque.addLast(deque.peekFirst());
                deque.pollFirst();
            }
            return deque.pollFirst();
        }
        
        public int top() {
            return deque.peekLast();
        }
        
        public boolean empty() {
            return deque.isEmpty();
        }
    }
    
    /**
     * Your MyStack object will be instantiated and called as such:
     * MyStack obj = new MyStack();
     * obj.push(x);
     * int param_2 = obj.pop();
     * int param_3 = obj.top();
     * boolean param_4 = obj.empty();
     */
    
    /**
       两个队列
    */
    class MyStack {
        Queue<Integer> queue1;
        Queue<Integer> queue2;
        public MyStack() {
            queue1=new LinkedList<>();
            queue2=new LinkedList<>();
        }
        
        public void push(int x) {
            queue1.offer(x);
            //将queue2元素全部转给queue1
            while(!queue2.isEmpty()){
                queue1.offer(queue2.poll());
            }
            //交换queue1和queue2,使得queue1没有push的情况下始终为空
            Queue temp=queue1;
            queue1=queue2;
            queue2=temp;
        }
        
        public int pop() {
            return queue2.poll();
        }
        
        public int top() {
            return queue2.peek();
        }
        
        public boolean empty() {
            return queue2.isEmpty();
        }
    }
    

155. 最小栈

  • 题目描述

    image.png

  • 题解

    每个元素入栈时,同时在辅助栈中保存当前栈中的最小值

    fig1

    class MinStack {
        Stack<Integer> stack;
        Stack<Integer> min;
        public MinStack() {
            stack=new Stack<>();
            min=new Stack<>();
            min.push(Integer.MAX_VALUE);
        }
        
        public void push(int val) {
            stack.push(val);
            min.push(Math.min(val,min.peek()));
        }
        
        public void pop() {
            stack.pop();
            min.pop();
        }
        
        public int top() {
            return stack.peek();
        }
        
        public int getMin() {
            return min.peek();
        }
    }
    
    /**
     * Your MinStack object will be instantiated and called as such:
     * MinStack obj = new MinStack();
     * obj.push(val);
     * obj.pop();
     * int param_3 = obj.top();
     * int param_4 = obj.getMin();
     */
    

20. 有效的括号

  • 题目描述

    image.png

  • 题解

    /**
    	遇到左括号入栈对应的右括号
    	如果栈为空或者遇到右括号出栈时不同时,false
    */
    class Solution {
        public boolean isValid(String s) {
            Stack<Character> stack=new Stack<>();
            for(int i=0;i<s.length();i++){
                if(s.charAt(i)=='('){
                    stack.push(')');
                }else if(s.charAt(i)=='{'){
                    stack.push('}');
                }else if(s.charAt(i)=='['){
                    stack.push(']');
                }else{
                    if(stack.isEmpty() || s.charAt(i)!=stack.pop()){
                        return false;
                    }
                }
            }
            return stack.isEmpty();
        }
    }
    

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

  • 题目描述

    image.png

  • 题解1:栈

    class Solution {
        public String removeDuplicates(String s) {
            //ArrayDeque会比LinkedList在除了删除元素这一点外会快一点
            ArrayDeque<Character> deque=new ArrayDeque<>();
            for(int i=0;i<s.length();i++){
                if(deque.isEmpty() || deque.peek()!=s.charAt(i)){
                    deque.push(s.charAt(i));
                }else{
                    deque.pop();
                }
            }
            String str="";
            while(!deque.isEmpty()){
                str=deque.pop()+str;
            }
            return str;
        }
    }
    
  • 题解2:双指针

    class Solution {
        public String removeDuplicates(String s) {
            char[] ch=s.toCharArray();
            int fast=0;
            int slow=0;
            while(fast<ch.length){
              	//直接用fast指针覆盖slow指针的值
                ch[slow]=ch[fast];
              	//slow遇到相同的值,则跳过,即slow退一步,下次循环就会被直接覆盖
                if(slow>0 && ch[slow]==ch[slow-1]){
                    slow--;
                }else{
                    slow++;
                }
                fast++;
            }
            return new String(ch,0,slow);
        }
    }
    

150. 逆波兰表达式求值

  • 题目描述

    image.png

    image.png

  • 题解

    image.png

    150.逆波兰表达式求值

    class Solution {
        public int evalRPN(String[] tokens) {
            Deque<Integer> stack=new LinkedList<>();
            for(int i=0;i<tokens.length;i++){
              	//注意 - 和/ 需要特殊处理
                if("+".equals(tokens[i])){
                    stack.push(stack.pop()+stack.pop());
                }else if("-".equals(tokens[i])){
                    stack.push(-stack.pop()+stack.pop());
                }else if("*".equals(tokens[i])){
                    stack.push(stack.pop()*stack.pop());
                }else if("/".equals(tokens[i])){
                    int temp=stack.pop();
                    stack.push(stack.pop()/temp);
                }else{
                    stack.push(Integer.valueOf(tokens[i]));
                }
            }
            return stack.pop();
        }
    }
    

71. 简化路径

  • 题目描述

    image.png

    image.png

  • 题解

    image.png

    class Solution {
        public String simplifyPath(String path) {
            ArrayDeque<String> stack=new ArrayDeque<>();
            String[] names=path.split("/");
            for(String str:names){
                if("..".equals(str)){
                    if(!stack.isEmpty()) {
                        stack.pollLast();
                    }
                }else if(str.length()>0 && !".".equals(str)){
                    stack.offer(str);
                }
            }
            StringBuilder sb=new StringBuilder();
            if(stack.isEmpty()){
                sb.append("/");
            }else{
                while(!stack.isEmpty()){
                    sb.append("/");
                    sb.append(stack.poll());
                }
            }
            return sb.toString();
        }
    }
    

239. 滑动窗口最大值

  • 题目描述

    image.png

  • 题解

    /**
        利用双端队列实现单调队列,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
        单调递减队列
     */
    class Solution {
        public int[] maxSlidingWindow(int[] nums, int k) {
            Deque<Integer> deque=new LinkedList<>();
            int[] res=new int[nums.length-k+1];
            int index=0;
            for(int i=0;i<nums.length;i++){
                //单调队列的头元素要满足在[i-k+1,i]区间,不满足直接弹出
                while(!deque.isEmpty() && deque.peek()<i-k+1){
                    //移除队头元素
                    deque.poll();
                }
                //因为单调,所以每次进入的元素要保证比队尾元素大,不满足则需要弹出
              	//注意这里比较的队尾元素
                while(!deque.isEmpty() && nums[deque.peekLast()]<nums[i]){
                    //移除队尾元素
                    deque.pollLast();
                }
                //加入队尾
                deque.offer(i);
                //i增长到符合第一个k的范围,之后没滑动一步都需要记录最大值
                if(i>=k-1){
                    res[index++]=nums[deque.peek()];
                }
            }
            return res;
        }
    }
    

347. 前 K 个高频元素

  • 题目描述

    image.png

  • 题解

    image.png

    堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆

    经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素)

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

    image.png

    class Solution {
        public int[] topKFrequent(int[] nums, int k) {
            int[] res=new int[k];
            Map<Integer,Integer> map=new HashMap<>();
            //统计元素出现频率
            for(int num:nums){
                map.put(num,map.getOrDefault(num,0)+1);
            }
            //对频率排序
            //定义一个小顶堆,大小为k
            Set<Map.Entry<Integer,Integer>> entries=map.entrySet();
            PriorityQueue<Map.Entry<Integer,Integer>> queue=new PriorityQueue<>((q1,q2)->q1.getValue()-q2.getValue());
            //用固定大小为k的小顶堆,扫面所有频率的数值
            for(Map.Entry<Integer,Integer> entry:entries){
                queue.offer(entry);
                //如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                if(queue.size()>k){
                    queue.poll();
                }
            }
            //找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
            for(int i=k-1;i>=0;i--){
                res[i]=queue.poll().getKey();
            }
            return res;
        }
    }
    

栈与队列总结

  • 了解了栈与队列基础之后,那么可以用栈与队列:栈实现队列 (opens new window)栈与队列:队列实现栈 (opens new window)来练习一下栈与队列的基本操作

    • 栈实现队列:两个栈一个负责入队一个负责出队,出队栈拷贝入队栈
    • 队列实现栈:可以使用一个双端队列简化,一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出元素就是栈的顺序了
  • 递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因

  • 字符串去重问题:1047. 删除字符串中的所有相邻重复项

    思路就是可以把字符串顺序放到一个栈中,然后如果相同的话 栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了

  • 逆波兰表达式问题:本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算

  • 滑动窗口的最大值单调队列

    • 主要思想是队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的
    • 单调队列不是一成不变的,而是不同场景不同写法,总之要保证队列里单调递减或递增的原则,所以叫做单调队列
  • 求前K个高频元素优先级队列

    • 优先级队列:一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。

    • 堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆

CodeTop系列

重复且重要的题目

  • 最小栈、用栈实现队列、用队列实现栈、有效的括号、删除字符串中的重复项、二叉树的前序中序遍历

496. 下一个更大元素 I

  • 题目描述

    image.png

  • 题解

    单调栈

    class Solution {
        public int[] nextGreaterElement(int[] nums1, int[] nums2) {
            int[] res=new int[nums1.length];
            Stack<Integer> stack=new Stack<>();
            Map<Integer,Integer> map=new HashMap<>();
            for(int i=0;i<nums2.length;i++){
                while(!stack.isEmpty() && stack.peek()<nums2[i]){
                    //记录出栈元素的右边第一个大于它的值
                    map.put(stack.pop(),nums2[i]);
                }
                stack.push(nums2[i]);
            }
            //遍历nums1 找map中对应的值
            for(int i=0;i<nums1.length;i++){
                res[i]=map.getOrDefault(nums1[i],-1);
            }
            return res;
        }
    }
    

739. 每日温度

  • 题目描述

    image.png

  • 题解

    /**
        单调递增栈,当前元素比栈顶元素小,就入栈
        否则出栈并计算下标差,直到满足单调递增再次入栈
     */
    class Solution {
        public int[] dailyTemperatures(int[] temperatures) {
            Deque<Integer> stack=new LinkedList();
            int[] res=new int[temperatures.length];
            for(int i=0;i<temperatures.length;i++){
                while(!stack.isEmpty() && temperatures[i]>temperatures[stack.peek()]){
                    int preIndex=stack.pop();
                    res[preIndex]=i-preIndex;
                }
                //注意放入的下标i
                stack.push(i);
            }
            return res;
        }
    }
    

402. 移掉 K 位数字

  • 题目描述

    image.png

  • 题解

  • 单调栈+贪心

    从左到右,找第一个比后面大的字符,删除,清零,k次扫描

    class Solution {
        public String removeKdigits(String num, int k) {
            Deque<Character> deque=new LinkedList<>();
            char[] sArr=num.toCharArray();
            for(int i=0;i<sArr.length;i++){
                //当且仅当K>0 并且队尾元素大于要入队的元素的时候就把队尾元素移除掉
                while(!deque.isEmpty() && k>0 && deque.peekLast()>sArr[i]){
                    deque.pollLast();
                    k--;
                }
                deque.offer(sArr[i]);
            }
            //此时如果K还大于0 队列里面的元素已经为单调不降了。则最后依次移除队列尾部剩余的k数次即可,
            //拿123456728 k=7 举例说明
            //入队完后 队列里面为1228 此时k=2 所以还需要依次移除尾部2和8  剩余12即为最小
            for(int i=0;i<k;i++){
                deque.pollLast();
            }
            StringBuilder res=new StringBuilder();
            boolean falg=true;
            //从队列头部取出所有元素
            while(!deque.isEmpty()){
                char ch=deque.poll();
                //防止前导0 也就是队头第一个元素==0 则需要跳过
                if(falg && ch=='0'){
                    continue;
                }
                falg=false;
                res.append(ch);
            }
            return res.length()==0?"0":res.toString();
        }
    }
    

227. 基本计算器 II

  • 题目描述

    image.png

  • 题解

    遍历字符串 ss,并用变量 preSign 记录每个数字之前的运算符,对于第一个数字,其之前的运算符视为加号。每次遍历到数字末尾时,根据preSign 来决定计算方式:

    加号:将数字压入栈; 减号:将数字的相反数压入栈; 乘除号:计算数字与栈顶元素,并将栈顶元素替换为计算结果 代码实现中,若读到一个运算符,或者遍历到字符串末尾,即认为是遍历到了数字末尾。处理完该数字后,更新 preSign 为当前遍历的字符

    class Solution {
        public int calculate(String s) {
            int presign='+';
            int num=0;
            Deque<Integer> stack=new LinkedList<>();
            for(int i=0;i<s.length();i++){
                if(Character.isDigit(s.charAt(i))){
                    //保存当前数字,如:12是两个字符,需要进位累加
                    //记录当前数字。先减,防溢出
                    num=num*10-'0'+s.charAt(i);
                }
                if(s.charAt(i) != ' ' && !Character.isDigit(s.charAt(i)) || i==s.length()-1){
                    switch(presign){
                        case '+':
                            stack.push(num);
                            break;
                        case '-':
                            stack.push(-num);
                            break;
                        case '*':
                            stack.push(stack.pop()*num);
                            break;
                        default:
                            stack.push(stack.pop()/num);
                    }
                    presign=s.charAt(i);
                    num=0;
                }
            }
            int res=0;
            while(!stack.isEmpty()){
                res+=stack.pop();
            }
            return res;
        }
    }
    

71. 简化路径

  • 题目描述

    image.png

  • 题解

    /**
        ../   返回上一级目录
         ./   当前的目录
         //   视为一个 /
          /   在结尾
     */
    class Solution {
        public String simplifyPath(String path) {
            ArrayDeque<String> deque=new ArrayDeque<>();
            String[] strArr=path.split("/");
            for(String str:strArr){
                if("..".equals(str)){
                    if(!deque.isEmpty()){
                        deque.pollLast();
                    }
                }else if(str.length()>0 && !".".equals(str)){
                    deque.offer(str);
                }
            }
            StringBuilder sb=new StringBuilder();
            if(deque.isEmpty()){
                sb.append("/");
            }else{
                while(!deque.isEmpty()){
                    sb.append("/");
                    sb.append(deque.poll());
                }
            }
            return sb.toString();
        }
    }
    

503. 下一个更大元素 II

  • 题目描述

    image.png

  • 题解

    class Solution {
        public int[] nextGreaterElements(int[] nums) {
            int n=nums.length;
            int[] res=new int[n];
            Arrays.fill(res,-1);
            Deque<Integer> stack=new LinkedList<>();
            for(int i=0;i<2*n-1;i++){
                while(!stack.isEmpty() && nums[stack.peek()]<nums[i%n]){
                    int index=stack.pop();
                    res[index]=nums[i%n];
                }
                stack.push(i%n);
            }
            return res;
        }
    }
    

103. 二叉树的锯齿形层序遍历

  • 题目描述

    image.png

  • 题解

    /**
     *  dfs,对应层判断一下奇偶,决定在头插还是尾插
     */
    class Solution {
        public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
            List<List<Integer>> res=new ArrayList<>();
            if(root==null) return res;
            Deque<TreeNode> queue=new LinkedList<>();
            queue.offer(root);
            int flag=0;
            while(!queue.isEmpty()){
                int size=queue.size();
                List<Integer> temp=new ArrayList<>();
                if(flag%2==0){
                    for(int i=0;i<size;i++){
                        TreeNode node=queue.poll();
                        temp.add(node.val);
                        if(node.left!=null) queue.offer(node.left);
                        if(node.right!=null) queue.offer(node.right);
                    }
                }else{
                    for(int i=0;i<size;i++){
                        TreeNode node=queue.pollLast();
                        temp.add(node.val);
                        if(node.right!=null) queue.offerFirst(node.right);
                        if(node.left!=null) queue.offerFirst(node.left);
                    }
                }
                res.add(new ArrayList(temp));
                flag++;
            }
            return res;
        }
    }
    

735. 行星碰撞

  • 题目描述

    image.png

  • 题解

    /**
    	使用栈模拟行星碰撞,从左往右遍历行星数组asteroids,当我们遍历到行星aster 时,使用变量 \alive 记录行	  	星aster是否还存在(即未爆炸)
    	当行星存在且aster<0,栈顶元素非空且大于0时,说明两个行星相互向对方移动:如果栈顶元素大于等于−aster,则	行星aster发生爆炸,将alive置为false;如果栈顶元素小于等于−aster,则栈顶元素表示的行星发生爆炸,执行	出栈操作。重复以上判断直到不满足条件,如果最后alive 为真,说明行星aster不会爆炸,则将aster入栈
    */
    class Solution {
        public int[] asteroidCollision(int[] asteroids) {
            Deque<Integer> stack=new LinkedList<>();
            for(int aster:asteroids){
                //标识行星是否存活
                boolean flag=true;
                while(flag && aster<0 && !stack.isEmpty() && stack.peek()>0){
                    flag=stack.peek()< -aster;
                    if(stack.peek() <= -aster){
                        //栈顶爆炸 出栈
                        stack.pop();
                    }
                }
                if(flag){
                    stack.push(aster);
                }
            }
            int[] res=new int[stack.size()];
            int index=0;
            while(!stack.isEmpty()){
                res[index++]=stack.pollLast();
            }
            return res;
        }
    }
    

394. 字符串解码

  • 题目描述

    image.png

  • 题解

    /**
         * 双栈解法:
         * 准备两个栈,一个存放数字,一个存放字符串
         * 遍历字符串分4中情况
         * 一、如果是数字 将字符转成整型数字 注意数字不一定是个位 有可能是十位,百位等 
         			 所以digit = digit*10 + ch - '0';
         * 二、如果是字符 直接将字符放在临时字符串中
         * 三、如果是"[" 将临时数字和临时字符串入栈
         * 四、如果是"]" 将数字和字符串出栈 此时临时字符串res = 出栈字符串 + 出栈数字*res
         */
    class Solution {
        public String decodeString(String s) {
            Deque<Integer> digitStack=new LinkedList<>();
            Deque<StringBuilder> stringStack=new LinkedList<>();
            int digit=0;
            StringBuilder res=new StringBuilder();
            char[] sArr=s.toCharArray();
            for(int i=0;i<sArr.length;i++){
                if(sArr[i]=='['){
                    //如果是"[" 将临时数字和临时字符串入栈
                    digitStack.push(digit);
                    stringStack.push(res);
                    digit=0;
                    res=new StringBuilder();
                }else if(sArr[i]==']'){
                    //如果是"]" 将数字和字符串出栈 此时临时字符串res = 出栈字符串 + 出栈数字*res
                    StringBuilder temp=stringStack.poll();
                    int count=digitStack.poll();
                    for(int j=0;j<count;j++){
                        temp.append(res.toString());
                    }
                    res=temp;
                }else if(Character.isDigit(sArr[i])){
                    //如果是数字 将字符转成整型数字 ch-'0'
                    //注意数字不一定是个位 比如100[a] 所以digit要*10
                    digit=digit*10-'0'+sArr[i];
                }else{
                    //如果是字符 直接将字符放在临时字符串中
                    res.append(sArr[i]);
                }
            }
            return res.toString();
        }
    }
    

316. 去除重复字母

  • 题目描述

    image.png

  • 题解

    class Solution {
        public String removeDuplicateLetters(String s) {
            //遇到一个新字符 如果比栈顶小 并且在新字符后面还有和栈顶一样的 就把栈顶的字符抛弃
            Deque<Character> stack=new LinkedList<>();
            for(int i=0;i<s.length();i++){
                Character ch=s.charAt(i);
                if(stack.contains(ch)) continue;
                while(!stack.isEmpty() && stack.peek()>ch && s.indexOf(stack.peek(),i)!=-1){
                    //indexOf(int ch, int fromIndex) 
                    //返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引
                    //如果此字符串中没有这样的字符,则返回 -1
                    stack.pop();
                }
                stack.push(ch);
            }
            char[] res=new char[stack.size()];
            int index=0;
            while(!stack.isEmpty()){
                res[index++]=stack.pollLast();
            }
            return new String(res);
        }
    }
    

856. 括号的分数

  • 题目描述

    image.png

  • 题解

    字符串 S 中的每一个位置都有一个“深度”,即该位置外侧嵌套的括号数目。例如,字符串 (()(.())) 中的 . 的深度为 2,因为它外侧嵌套了 2 层括号:((.))。

    我们用一个栈来维护当前所在的深度,以及每一层深度的得分。当我们遇到一个左括号 ( 时,我们将深度加一,并且新的深度的得分置为 0。当我们遇到一个右括号 ) 时,我们将当前深度的得分乘二并加到上一层的深度。这里有一种例外情况,如果遇到的是 (),那么只将得分加一。

    下面给出了字符串 (()(())) 每次对应的栈的情况:

    [0, 0] ( [0, 0, 0] (( [0, 1] (() [0, 1, 0] (()( [0, 1, 0, 0] (()(( [0, 1, 1] (()(() [0, 3] (()(()) [6] (()(()))

    class Solution {
        public int scoreOfParentheses(String s) {
            Deque<Integer> stack=new LinkedList<>();
            stack.push(0);
            for(char ch:s.toCharArray()){
                if(ch=='('){
                    stack.push(0);
                }else{
                    int v=stack.pop();
                    int w=stack.pop();
                    stack.push(w+Math.max(v*2,1));
                }
            }
            return stack.pop();
        }
    }
    

946. 验证栈序列

  • 题目描述

    image.png

  • 题解

    贪心:

    将 pushed 队列中的每个数都 push 到栈中,同时检查这个数是不是 popped 序列中下一个要 pop 的值,如果是就把它 pop 出来。

    最后,检查不是所有的该 pop 出来的值都是 pop 出来了

    class Solution {
        public boolean validateStackSequences(int[] pushed, int[] popped) {
            Deque<Integer> stack=new LinkedList<>();
            int j=0;
            for(int i=0;i<pushed.length;i++){
                stack.push(pushed[i]);
                while(!stack.isEmpty() && stack.peek()==popped[j]){
                    j++;
                    stack.pop();
                }
            }
            return stack.isEmpty();
        }
    }
    

剑指 Offer 59 - II. 队列的最大值

  • 题目描述

    image.png

  • 题解

    维护一个单调队列

    那么如何高效实现一个始终递减的队列呢?我们只需要在插入每一个元素 value 时,从队列尾部依次取出比当前元素 value 小的元素,直到遇到一个比当前元素大的元素 value' 即可

    上面的过程保证了只要在元素 value 被插入之前队列递减,那么在 value 被插入之后队列依然递减。 而队列的初始状态(空队列)符合单调递减的定义 由数学归纳法可知队列将会始终保持单调递减。 上面的过程需要从队列尾部取出元素,因此需要使用双端队列来实现。另外我们也需要一个辅助队列来记录所有被插入的值,以确定 pop_front 函数的返回值

    保证了队列单调递减后,求最大值时只需要直接取双端队列中的第一项即可

    class MaxQueue {
        Queue<Integer> queue;
        Deque<Integer> deque;
        public MaxQueue() {
            queue=new LinkedList<>();
            deque=new LinkedList<>();
        }
        
        public int max_value() {
            return deque.isEmpty()?-1:deque.peek();
        }
        
        public void push_back(int value) {
            queue.offer(value);
            while(!deque.isEmpty() && value>deque.peekLast()){
                deque.pollLast();
            }
            deque.offer(value);
        }
        
        public int pop_front() {
            if(queue.isEmpty()) return -1;
            int val=queue.poll();
            if(deque.peek().equals(val)){
                deque.pop();
            }
            return val;
        }
    }
    

331. 验证二叉树的前序序列化

  • 题目描述

    image.png

  • 题解

    fig1

    我们使用栈来维护槽位的变化。栈中的每个元素,代表了对应节点处剩余槽位的数量,而栈顶元素就对应着下一步可用的槽位数量。当遇到空节点时,仅将栈顶元素减 11;当遇到非空节点时,将栈顶元素减 11 后,再向栈中压入一个 22。无论何时,如果栈顶元素变为 00,就立刻将栈顶弹出。

    遍历结束后,若栈为空,说明没有待填充的槽位,因此是一个合法序列;否则若栈不为空,则序列不合法。此外,在遍历的过程中,若槽位数量不足,则序列不合法

    class Solution {
        public boolean isValidSerialization(String preorder) {
            int n = preorder.length();
            int i = 0;
            Deque<Integer> stack = new LinkedList<Integer>();
            stack.push(1);
            while (i < n) {
                if (stack.isEmpty()) {
                    return false;
                }
                if (preorder.charAt(i) == ',') {
                    i++;
                } else if (preorder.charAt(i) == '#'){
                    int top = stack.pop() - 1;
                    if (top > 0) {
                        stack.push(top);
                    }
                    i++;
                } else {
                    // 读一个数字
                    while (i < n && preorder.charAt(i) != ',') {
                        i++;
                    }
                    int top = stack.pop() - 1;
                    if (top > 0) {
                        stack.push(top);
                    }
                    stack.push(2);
                }
            }
            return stack.isEmpty();
        }
    }
    

907. 子数组的最小值之和

  • 题目描述

    image.png

  • 题解

    image.png

    class Solution {
        public int sumSubarrayMins(int[] A) {
            int MOD = 1_000_000_007;
    
            Stack<RepInteger> stack = new Stack();
            int ans = 0, dot = 0;
            for (int j = 0; j < A.length; ++j) {
                // Add all answers for subarrays [i, j], i <= j
                int count = 1;
                while (!stack.isEmpty() && stack.peek().val >= A[j]) {
                    RepInteger node = stack.pop();
                    count += node.count;
                    dot -= node.val * node.count;
                }
                stack.push(new RepInteger(A[j], count));
                dot += A[j] * count;
                ans += dot;
                ans %= MOD;
            }
    
            return ans;
        }
    }
    
    class RepInteger {
        int val, count;
        RepInteger(int v, int c) {
            val = v;
            count = c;
        }
    }
    

456. 132 模式

  • 题目描述

    image.png

  • 题解

    class Solution {
        public boolean find132pattern(int[] nums) {
            int len=nums.length;
            //存132中的2的值
            int num2=Integer.MIN_VALUE;
            //栈中存放132中3的值
            Stack<Integer> stack=new Stack<>();
            if(nums.length<3) return false;
            for(int i=len-1;i>=0;i--){
                //出现1 则返回true
                if(nums[i]<num2) return true;
                while(!stack.isEmpty() && stack.peek()<nums[i]){
                    //更新num2,num2为栈中小于当前值的最大元素
                    num2=stack.pop();
                }
                stack.push(nums[i]);
            }
            return false;
        }
    }
    

173. 二叉搜索树迭代器

  • 题目描述

    image.png

  • 题解

    class BSTIterator {
    
        private TreeNode cur;
        private Deque<TreeNode> stack; // 依次存放待遍历的节点
        // 中序遍历: 左 》 根 》 右
        public BSTIterator(TreeNode root) {
            cur = root;
            stack  = new LinkedList<TreeNode>();
        }
        public int next() {
            while (cur!=null){// 当前节点不为空 不停遍历左子树 直到 cur = null跳出
                stack.push(cur); // 此时栈顶元素是不为空的cur 左子树已经遍历了  “左”为null
                cur = cur.left; //只需要从栈顶弹出当前节点 去遍历“根”
            }
            cur = stack.pop();// 如果cur为空 那么从栈顶弹出自己的父节点 返回对应的值 
            //  栈顶元素弹出 表示已经遍历过
            int val = cur.val;
            cur = cur.right;// “根”遍历完  遍历“右”  循环往复 右子树又是先左后根 再右
            return val;
        }
        public boolean hasNext() {
            return cur!=null || !stack.isEmpty();
            // 栈为空 不代表遍历完毕  比如整棵树的左子树遍历完毕 根也遍历完毕
            // 此时 cur = root(根节点).right   栈为空  如果cur不为空  说明右子树还未遍历 hasNext为true.
        }
    }
    

补充题24双栈排序

  • 题目描述

    image.png

  • 题解

    image.png

1249. 移除无效的括号

  • 题目描述

    image.png

  • 题解

    class Solution {
        public String minRemoveToMakeValid(String s) {
            Stack<Integer> stack = new Stack<>();
            StringBuilder build = new StringBuilder();
            boolean[] inArr = new boolean[s.length()];
    
            for(int i = 0; i < s.length(); i++){
                if(s.charAt(i) == '('){
                    stack.push(i);
                    inArr[i] = true;
                }else if(s.charAt(i) == ')'){
                    if(!stack.isEmpty()){
                        inArr[stack.pop()] = false;
                    }else{
                        inArr[i] = true;
                    }
                }
            }
    
            for(int i = 0; i < inArr.length; i++){
                if(!inArr[i]) build.append(s.charAt(i));
            }
            return build.toString();
        }
    }
    

1190. 反转每对括号间的子串

  • 题目描述

    image.png

  • 题解

    我们从左到右遍历该字符串,使用字符串str 记录当前层所遍历到的小写英文字母。对于当前遍历的字符:

    如果是左括号,将str插入到栈中,并将str置为空,进入下一层;

    如果是右括号,则说明遍历完了当前层,需要将str反转,返回给上一层。具体地,将栈顶字符串弹出,然后将反转后的str 拼接到栈顶字符串末尾,将结果赋值给str

    如果是小写英文字母,将其加到str末尾

    注意到我们仅在遇到右括号时才进行字符串处理,这样可以保证我们是按照从括号内到外的顺序处理字符串

    class Solution {
        public String reverseParentheses(String s) {
            Deque<String> stack = new LinkedList<String>();
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < s.length(); i++) {
                char ch = s.charAt(i);
                if (ch == '(') {
                    stack.push(sb.toString());
                    sb.setLength(0);
                } else if (ch == ')') {
                    sb.reverse();
                    sb.insert(0, stack.pop());
                } else {
                    sb.append(ch);
                }
            }
            return sb.toString();
        }
    }