代码随想录刷题day10丨栈与队列理论基础,232.用栈实现队列,225. 用队列实现栈 , 20. 有效的括号, 1047. 删除字符串中的所有相邻重复项
1.栈与队列理论基础
-
栈(Stack)
- 定义:栈是一种遵循“后进先出”(LIFO, Last In First Out)原则的线性数据结构。这意味着最后添加的元素最先被移除。
- 基本操作:
- 入栈(Push):将一个元素添加到栈的顶端。
- 出栈(Pop):移除并返回栈顶元素。
- 查看栈顶元素(Peek/Top):获取但不移除栈顶元素。
- 判断栈是否为空(IsEmpty):检查栈中是否没有元素。
-
队列(Queue)
- 定义:队列是一种遵循“先进先出”(FIFO, First In First Out)原则的线性数据结构。这意味着第一个添加的元素最先被移除。
- 基本操作:
- 入队(Enqueue):将一个元素添加到队列的末尾。
- 出队(Dequeue):移除并返回队列的第一个元素。
- 查看队头元素(Front/Peek):获取但不移除队列的第一个元素。
- 判断队列是否为空(IsEmpty):检查队列中是否没有元素。
-
栈与队列的比较
- 访问顺序:
- 栈:LIFO,最后加入的元素最先被移除。
- 队列:FIFO,第一个加入的元素最先被移除。
- 应用场景:
- 栈适用于需要后进先出逻辑的场景,如递归、括号匹配等。
- 队列适用于需要按顺序处理数据的场景,如任务调度、广度优先搜索等。
- 访问顺序:
2.题目
2.1用栈实现队列
-
题目链接:232. 用栈实现队列 - 力扣(LeetCode)
-
文档讲解:https://programmercarl.com/0232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.html
-
解题思路:借助另一个栈
-
如果仅仅用一个栈,是一定不行的,所以需要两个栈一个输入栈,一个输出栈
-
在push数据的时候,只要数据放进输入栈就好,但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。
-
最后如何判断队列为空呢?
- 如果进栈和出栈都为空的话,说明模拟的队列为空了。
-
图示:
-
-
代码:
//时间复杂度: push和empty为O(1), pop和peek为O(n) //空间复杂度: O(n) class MyQueue { 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() { inToOut(); return stackOut.pop(); } public int peek() { inToOut(); return stackOut.peek(); } public boolean empty() { return stackIn.isEmpty() && stackOut.isEmpty(); } 如果stackOut为空,那么将stackIn中的元素全部放到stackOut中 private void inToOut(){ if(!stackOut.isEmpty()){ return; } while(!stackIn.isEmpty()){ stackOut.push(stackIn.pop()); } } }
-
总结:
- 一定要懂得复用,功能相近的函数要抽象出来,不要大量的复制粘贴,很容易出问题!
2.2用队列实现栈
-
题目链接:225. 用队列实现栈 - 力扣(LeetCode)
-
文档讲解:https://programmercarl.com/0225.%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.html
-
解题思路:把队列里的最后一个元素之前的所有元素一个个弹出来再加进去就行
-
代码:
//时间复杂度: pop为O(n),其他为O(1) //空间复杂度: O(n) class MyStack { Queue<Integer> queue; public MyStack() { queue = new LinkedList<>(); } public void push(int x) { queue.add(x); } public int pop() { rePosition(); return queue.poll();// poll()方法移除首元素 } public int top() { rePosition(); int result = queue.poll(); // 移除并获取队列前面的元素(即栈的栈顶元素) queue.add(result);// 将刚才移除的元素重新加入到队列的末尾,以恢复队列的原始状态 return result; } public boolean empty() { return queue.isEmpty(); } public void rePosition(){ int size = queue.size(); size--;// 确保最后一个元素不会被移动 while(size > 0){ queue.add(queue.poll()); size--; } } }
-
总结:
- 一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。
2.3有效的括号
-
-
文档讲解:https://programmercarl.com/0020.%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.html
-
解题思路:模拟三种不匹配的情况
-
第一种:遍历遇到左括号就在栈里面放右括号,遇到右括号就出栈一个右括号
-
第二种:遍历遇到左括号就在栈里面放右括号,遇到右括号判断下右括号的类型和栈里面的右括号类型匹配不匹配
-
第三种:遍历到某个类型的右括号时,发现栈此时为空
-
-
代码:
//时间复杂度: O(n) //空间复杂度: O(n) class Solution { public boolean isValid(String s) { Stack<Character> stack = new Stack<>(); if (s.length() % 2 != 0) { return false; } 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() || stack.peek() != s.charAt(i)) { return false; 如果栈为空或者栈顶元素与当前字符不匹配,返回 false }else { // 如果栈顶元素与当前字符匹配,弹出栈顶元素 stack.pop(); } } // 如果遍历完字符串后栈为空,说明所有括号都匹配 return stack.isEmpty(); } }
-
总结:
- 该题可以做剪枝操作:字符串里面的字符为奇数就不可能匹配
2.4删除字符串中的所有相邻重复项
-
题目链接:1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)
-
文档讲解:https://programmercarl.com/1047.%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.html
-
解题思路:
- 法一:遍历字符串,往栈里面加元素,碰到相同的就弹出元素,遍历结束剩余的元素就是最终的结果,就是要记得倒一下顺序
-
法二:拿字符串res直接作为栈,省去了栈还要转为字符串的操作
-
代码:
//时间复杂度: O(n) //空间复杂度: O(1),返回值不计空间复杂度 class Solution { public String removeDuplicates(String s) { // 将 res 当做栈 // 也可以用 StringBuilder 来修改字符串,速度更快 StringBuffer res = new StringBuffer(); int top = -1; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (top >= 0 && res.charAt(top) == c) { res.deleteCharAt(top); top--; } else { res.append(c); top++; } } return res.toString(); } }
-
总结:
-
要知道栈为什么适合做这种类似于爱消除的操作,因为栈帮助我们记录了 遍历数组当前元素时候,前一个元素是什么
-
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
-
在企业项目开发中,尽量不要使用递归!在项目比较大的时候,由于参数多,全局变量等等,使用递归很容易判断不充分return的条件,非常容易无限递归(或者递归层级过深),造成栈溢出错误(这种问题还不好排查!)
-