算法训练--栈与队列
基础概念
- 队列是先进先出,栈是先进后出
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. 用栈实现队列
-
题目描述
-
题解
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. 用队列实现栈
-
题目描述
-
题解
/** 可以使用两个队列 但可以用使用一个双端队列来进行优化 */ 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. 最小栈
-
题目描述
-
题解
每个元素入栈时,同时在辅助栈中保存当前栈中的最小值
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. 有效的括号
-
题目描述
-
题解
/** 遇到左括号入栈对应的右括号 如果栈为空或者遇到右括号出栈时不同时,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. 删除字符串中的所有相邻重复项
-
题目描述
-
题解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. 逆波兰表达式求值
-
题目描述
-
题解
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. 简化路径
-
题目描述
-
题解
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. 滑动窗口最大值
-
题目描述
-
题解
/** 利用双端队列实现单调队列,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可 单调递减队列 */ 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 个高频元素
-
题目描述
-
题解
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆
经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素)
这里要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素
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
-
题目描述
-
题解
单调栈
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. 每日温度
-
题目描述
-
题解
/** 单调递增栈,当前元素比栈顶元素小,就入栈 否则出栈并计算下标差,直到满足单调递增再次入栈 */ 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 位数字
-
题目描述
-
题解
-
单调栈+贪心
从左到右,找第一个比后面大的字符,删除,清零,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
-
题目描述
-
题解
遍历字符串 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. 简化路径
-
题目描述
-
题解
/** ../ 返回上一级目录 ./ 当前的目录 // 视为一个 / / 在结尾 */ 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
-
题目描述
-
题解
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. 二叉树的锯齿形层序遍历
-
题目描述
-
题解
/** * 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. 行星碰撞
-
题目描述
-
题解
/** 使用栈模拟行星碰撞,从左往右遍历行星数组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. 字符串解码
-
题目描述
-
题解
/** * 双栈解法: * 准备两个栈,一个存放数字,一个存放字符串 * 遍历字符串分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. 去除重复字母
-
题目描述
-
题解
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. 括号的分数
-
题目描述
-
题解
字符串 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. 验证栈序列
-
题目描述
-
题解
贪心:
将 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. 队列的最大值
-
题目描述
-
题解
维护一个单调队列
那么如何高效实现一个始终递减的队列呢?我们只需要在插入每一个元素 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. 验证二叉树的前序序列化
-
题目描述
-
题解
我们使用栈来维护槽位的变化。栈中的每个元素,代表了对应节点处剩余槽位的数量,而栈顶元素就对应着下一步可用的槽位数量。当遇到空节点时,仅将栈顶元素减 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. 子数组的最小值之和
-
题目描述
-
题解
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 模式
-
题目描述
-
题解
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. 二叉搜索树迭代器
-
题目描述
-
题解
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双栈排序
-
题目描述
-
题解
1249. 移除无效的括号
-
题目描述
-
题解
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. 反转每对括号间的子串
-
题目描述
-
题解
我们从左到右遍历该字符串,使用字符串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(); } }