LeetCode栈和队列专题
按照CS-Notes仓库的leetCode 题解来刷题
1. 用栈实现队列
232. Implement Queue using Stacks (Easy)
栈的顺序为后进先出,而队列的顺序为先进先出。
使用两个栈实现队列,一个元素需要经过两个栈才能出队列,在经过第一个栈时元素顺序被反转,经过第二个栈时再次被反转,此时就是先进先出顺序。
思路分析: 栈:后进先出;队列:先进先出。
要使用栈的标准API实现队列的标准API,使用一个栈必然会使得元素无法先进先出,
栈的特点就是可以颠倒入和出顺序,那么使用两个栈颠倒两次顺序,不就可以完成先进先出了嘛。
确定使用两个栈之后,就要考虑如何高效实现,如果其中一个栈只是用于颠倒元素顺序时候使用的暂时存放元素的栈,
那么每次入队操作,出队操作都需要遍历栈中元素,显然很慢。 如果其中一个栈负责存放入队的元素,记为 Stack<Integer> in;另外一个栈存放出队元素,记为Stack<Integer> out。 则每次入队操作只需要一次栈的push操作。时间复杂度为O(1)。
出队操作,当out为空时,需要将in中所有元素弹出并放入out中,
再将此时out中栈顶元素弹出即可,此时要遍历一次in中的元素。(不用了直接用while判断循环!!!while(!in.isEmpty){in非空则执行...}
如果out不为空时,直接弹出其栈顶元素即可。
从均摊的角度来看,n个元素的弹出,需要从in中popn次,再pushn次全部压入out,再popn次从而达到n次出队操作。
平均的操作为1次push加上2次pop。所以时间复杂度为O(n)。
peek操作与pop类似,只不过取元素时不删除栈中元素,其余一致。 判断是否为空,由于我们使用了两个栈来存放元素,所以必须两个栈都为空时in.empty() && out.empty()才能判断队列为空。
图示:用图示例 push(1),push(2),push(3),pop(),push(4),peek()。 作者:ustcyyw 链接:https://leetcode-cn.com/problems/implement-queue-using-stacks/solution/232java-da-bai-100tu-jie-by-ustcyyw/
class MyQueue { private Stack<Integer> in = new Stack<>(); private Stack<Integer> out = new Stack<>(); public void push(int x) { in.push(x); } public int pop() { in2out(); return out.pop(); } public int peek() { in2out(); return out.peek(); } private void in2out() {//封装了一个into out方法调用 if (out.isEmpty()) {//如果out空了 则执行循环让out进东西 while (!in.isEmpty()) {//只要in非空,就都进out out.push(in.pop()); } } } public boolean empty() { return in.isEmpty() && out.isEmpty(); } } /** * 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(); */
2. 用队列实现栈
225. Implement Stack using Queues (Easy)
在将一个元素 x 插入队列时,为了维护原来的后进先出顺序,需要让 x 插入队列首部。而队列的默认插入顺序是队列尾部,因此在将 x 插入队列尾部之后,需要让除了 x 之外的所有元素出队列,再入队列。
入队时调整元素顺序(官方标答)
(3进入时,前面的2和1从队头出去再从队尾进来,让3在队头)
前面是头,后面是尾
一个从尾部进入,则前面的都要从头部出去再从尾部进来一下。
class MyStack { private Queue<Integer> queue; /** Initialize your data structure here. */ public MyStack() { queue = new LinkedList<>();//我真不知道原来是这样new的。。。?不是Queue而是LinkedList } /** Push element x onto stack. */ public void push(int x) { // while(!queue.isEmpty){ // queue.pop(); // } // queue.push(x); // queue.push() 我写的逻辑不通了 每一个出去的都要用进来接收 queue.add(x); int cnt = queue.size(); while (cnt-- > 1) {//剩出x不要出了也不要入 它就变队头会第一个出了 queue.add(queue.poll()); } } /** Removes the element on top of the stack and returns that element. */ public int pop() { return queue.remove(); } /** Get the top element. */ public int top() { return queue.peek(); } /** Returns whether the stack is empty. */ public boolean empty() { return queue.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(); */
没学过数据结构就来做算法题。。
3. 最小值栈
155. Min Stack (Easy)
写一个栈类,能立即取出最小值
我以为不能用Stack。。。原来可以用,还能用两个。。看官方解答视频
一个数据栈,一个辅助栈,使辅助栈与数据栈同步
class MinStack { private Stack<Integer> data;//数据栈存数据 取数据 private Stack<Integer> MStack;//辅助栈 完成gerMin()方法 /** initialize your data structure here. */ public MinStack() { data = new Stack<>();//放到这里new是因为有真的用到该对象去掉用方法?全局对象变量不可以吗?不是 没区别 放外面一样。。。 MStack = new Stack<>(); } public void push(int x) { data.push(x); //辅助栈中要先判断放入的是否是比栈顶小再考虑放不放入 放入的是最小值 if(MStack.isEmpty()||x<=MStack.peek()){//必须是<= MStack.push(x); } } public void pop() { if(data.pop().equals(MStack.peek())){//红字部分表示data栈中已出已出 MStack.pop(); } } public int top() { return data.peek(); } public int getMin() { return MStack.peek(); } } /** * Your MinStack object will be instantiated and called as such: * MinStack obj = new MinStack(); * obj.push(x); * obj.pop(); * int param_3 = obj.top(); * int param_4 = obj.getMin(); */
4. 用栈实现括号匹配
20. Valid Parentheses (Easy)
"()[]{}"
Output : true
class Solution { public boolean isValid(String s) { //按字符串的索引依次放入队列中?然后双指针 。答案用的栈,s.toCharArray做接触式对对碰 //回文不行,他不是严格对称的 //简单代码 replace方法+递归 但耗费内存 if(s.contains("()")||s.contains("[]")||s.contains("{}")){//可能一次只能换一对。。。 return isValid(s.replace("()","").replace("[]","").replace("{}",""));//递归 }else{ return "".equals(s);//最后啥也不剩了就只有“”就是ture } } }
还是用栈做对对碰,算法上时间空间都nice
class Solution { public boolean isValid(String s) { if(s.length()%2!=0) return false; Stack<Character> stack = new Stack<>(); for(char c:s.toCharArray()){ if(c=='(') stack.push(')'); else if(c=='[') stack.push(']'); else if(c=='{') stack.push('}'); else if(stack.isEmpty()||c!=stack.pop())//不是左边的,且不是和放进去的右边长相一样 return false; } return stack.isEmpty();//不能直接return true 防止((这种情况判断不出来 所以判栈空最好,上面粉丝已经pop了 对对碰 } }
5. 数组中元素与下一个比它大的元素之间的距离
739. Daily Temperatures (Medium)
每日温度升温天数
Input: [73, 74, 75, 71, 69, 72, 76, 73]
Output: [1, 1, 4, 2, 1, 1, 0, 0]
class Solution { public int[] dailyTemperatures(int[] T) { int[] s = new int[T.length]; s[T.length-1]=0; // for(int i = 0; i<T.length()-1;i++){ // s[i] = T[i]>T[i+1]? // } // 应该从后往前推更简单 但是这样也可以,这个不动下个下下个比,用嵌套循环!! //因为最后一天显然不会再有升高的可能,结果直接为0。 //再看倒数第二天的温度,如果比倒数第一天低,那么答案显然为1,如果比倒数第一天高,又因为倒数第一天 //对应的结果为0,即表示之后不会再升高,所以倒数第二天的结果也应该为0。 //自此我们容易观察出规律,要求出第i天对应的结果,只需要知道第i+1天对应的结果就可以: //- 若T[i] < T[i+1],那么res[i]=1; //- 若T[i] > T[i+1] //- res[i+1]=0,那么res[i]=0; //- res[i+1]!=0,那就比较T[i]和T[i+1+res[i+1]](即将第i天的温度与第i+1天的温度进行比较)代码是再循环j for(i=T.length-2;i>=0;i--){ // if(T[i]>T[i+1]){ // if(res[i+1]!=0){ // } // }else{ // }编不下去我这能力 } } }
隔两天组会再做,我又没想起这种自己不动,和自己后面递推之间的比较,用嵌套循环啊啊啊啊啊啊!!!
从前往后,从后往前都可以。用栈来做更简单。三种方法我一种都没编出来。。。
从后往前其实最复杂:
class Solution { public int[] dailyTemperatures(int[] T) { int length = T.length; int[] s = new int[length]; for(int i =length-2;i>=0;i--){ int current = T[i]; for(int j = i+1;j<length;j=j+s[j]){//倒序的话这里不是用j++了。。。j还得是<length不是length-1。。。要用到最后一个呢 if(current < T[j]){ s[i] = j-i;//不是=1啊啊啊。。。此j是循环的更新以后不一定就是i+1了。。。所以用索引啊啊啊 break; }else if(s[j]==0){//current比s[j]大 s[j]是0的情况 s[i]=0; break; } //剩下的情况就是current比s[j]大 需要到j后面比它大的那个去比 需要进入循环 循环后那个j就变到后面去了
所以前面s[i]不能为1!!! } } return s; } }
从前往后反而容易理解:class Solution {
public int[] dailyTemperatures(int[] T) { int length = T.length; int[] s = new int[length]; s[length-1]=0;//最后一个是0 for(int i=0;i<length-1;i++){ int current = T[i]; for(int j=i+1;j<length;j++){//i+1只是初始值 因为前面的过去就不用考虑了 所以嵌套循环的j不要从0开始了要与i有关 if(T[j] > current){ s[i] = j-i;//线索在索引!!!! break; }//这次花括号一定要 达到if后跳出当前forj循环
//没写的else其他条件情况就代表不用管了因为本来就等于0 直接去循环j+1 找下一个满足if的 拉开差距变大了(嵌套循环,一个不动一个未达到循环终止前疯狂动) } } return s; } }
然后就是for循环里面写不需要循环的条件后,break就跳出当前循环。没写的else就是需要循环的条件!!!
用单调栈(栈小的才能push,大的索引进入while循环直接减去栈顶索引就是间隔天数)来做:
在遍历数组时用栈把数组中的数的索引存起来(栈中存的是索引),如果当前遍历的数比栈顶元素(由索引对应)来的大,说明栈顶元素的下一个比它大的数就是当前元素。看官方视频。。
class Solution { public int[] dailyTemperatures(int[] t) { int n = t.length; int[] dist = new int[n]; Stack<Integer> indexs = new Stack<>(); for (int curIndex = 0; curIndex < n; curIndex++) { while (!indexs.isEmpty() && t[curIndex] > t[indexs.peek()]) { //这里要用while做循环。不能用if!!!
if只会判断一次就走了,而while直到条件结果为false,才跳出来。这里这个 t[indexs.peek()]值是不断自动更新的。如果条件一直为ture,会产生很多s[preIndex] int preIndex = indexs.pop(); dist[preIndex] = curIndex - preIndex;//最终还是还是索引值!!! } indexs.push(curIndex);//这里就是循环里面必执行的,不管前面while或if满足或不满足
回到for循环
} return dist; } }
不自己写个代码真的不知道自己连if和while以及for都用不清楚。。。
1.do...while循环至少执行一次循环体.
2.而for,while循环必须先判断条件是否成立,然后决定是否执行循环体语句.
if 只做判断,判断一次之后,便不会再回来了
while 的话,循环,直到结果为false,才跳出来
第一:break语句通常用在循环语句和开关语句中,当break语句用于do-while、for、while循环语句中时,可使程序终止循环而执行循环后面的语句。
通常break语句总是与if语句联在一起,即满足条件时便跳出循环
注意:
1) break语句袭对百if-else的条件语句不起作用。
2) 在多层循环中, 一个break语句只向外跳一层。(前面那个只跳出j的嵌套for)
第二:continue语句的作度用是跳过循环本中剩余的语句而强行执行下一次循环。continue语句只用在for、while、do-while等循环体中,
常与if条件语句一起使用,用来加速循环。
其实就是continue跳过一次循环以及后面的语句,进行下次循环。
第三: return语句是将函数的值返回主调函数
return 表达式
或者为:
return (表达式)
放在for语句中一般就是直接返回了,执行到语句下面的均不执行了,包括往后的循环
6. 循环数组中比当前元素大的下一个元素(用栈来比数列的单调性)
503. Next Greater Element II (Medium)
Input: [1,2,1]
Output: [2,-1,2]
Explanation: The first 1's next greater number is 2;
The number 2 can't find next greater number;
The second 1's next greater number needs to search circularly, which is also 2.
与 739. Daily Temperatures (Medium) 不同的是,数组是循环数组,并且最后要求的不是距离而是下一个元素。
这里后面比current小的也是不用考虑的!!!为什么我总是有那么多if...其实根本不需要考虑啊。。。
但是我还是理解错意思了,他中间的不止要找后面的比较,若后面没有,那么前面的也是要找的。。
然后我对循环数组不熟,没有掌握碰到循环数据需要取余这个套路(这个套路就是因为前面后面都要比,所以干脆让外层嵌套i<2*length保证前后都能比到,然后后面那一半的索引是没有对应取值的,但是他的值就是前半段一样的。所以可以用前半段的索引取到值,就是T(i%length)的值是等于T(i)的,所以用T(i%length)就可以,i越界这个问题就解决了。我用嵌套循环做来着。。而且还前后开工做完全没想到其实可以后面加一遍(最多比两轮这个思想,需要取余)。。。所以一个下午也没成功。。
0924也可以的!!!
class Solution { public int[] nextGreaterElements(int[] nums) { int length = nums.length; int[] s = new int[length]; for(int i =0;i<length;i++){ int cur = nums[i]; for(int j=i+1;j<length*2;j++){ if(nums[j%length]-cur>0){//这里卡了一下 用到两倍都得对length取余不然会越界 s[i]=nums[j%length]; break; }else{ s[i]=-1; } } } return s; } }
官方答案还是用单调栈:(用于比数列元素单调性 不是规矩地按顺序填入 。。)刷题还是得b站看视频看过程。
class Solution { public int[] nextGreaterElements(int[] nums) { int length = nums.length; Stack<Integer> indexs = new Stack<>();//还是存的下标 int[] res = new int[length]; Arrays.fill(res, -1); for(int i=0;i<length*2;i++){//写成两遍 while(!indexs.isEmpty()&&nums[i%length]>nums[indexs.peek()]){//单调栈 同上一题 用while循环来完成判断插入栈中 int preIndex = indexs.pop(); //i对应的值它若大,就把栈顶拿出来存起来等于它,此时栈顶自动更新 res[preIndex] = nums[i%length];//因为我们只关心值,不关心位置,所以用取余。其实是相等的。但我们没有i>length的后半部分的值。 //用两遍是保证前面后面所有值都能比到 } if(i<length)//限制一下加入栈的大小!!!!循环后半部分i是多加的 indexs.add(i); } return res; } }