本文参考代码随想录提供的教程:栈与队列
栈和队列是编程中的核心数据结构,它们分别以先进后出和先进先出的特性,为算法设计和系统架构提供了基础支持。栈的特性在于它只能从一端进行数据的添加和移除操作,这使得它在处理递归、回溯和函数调用等场景中显得尤为重要。而队列则允许从一端添加数据,从另一端移除数据,这种结构在任务调度、缓冲处理等场合中发挥着关键作用。这两种结构虽然简单,但在实际应用中却有着广泛的用途和深远的影响。
232. 用栈实现队列
题目:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x)将元素 x 推到队列的末尾int pop()从队列的开头移除并返回元素int peek()返回队列开头的元素boolean empty()如果队列为空,返回true;否则,返回false
解题思路
使用双栈模拟队列,push时候将元素插入stk1;pop或取队首元素时,如果stk2为空就把stk1的全部插入stk2,负负得正,此时stk2栈顶是最先进入的元素了;如果stk2有元素就不需要把stk1的元素插进去了。
push操作的时间复杂度为 O(1)。pop和peek操作的时间复杂度在最坏情况下为 O(n),但平均情况下为 O(1)。empty操作的时间复杂度为 O(1)。- 空间复杂度为 O(n),其中 n 是队列中元素的数量。
class MyQueue:
def __init__(self):
self.stk1 = [] # push
self.stk2 = [] # pop
def push(self, x: int) -> None:
self.stk1.append(x)
def peek(self) -> int:
''' 弹出队首元素,如果s2为空,把s1全部插入s2 '''
# 确保stk2有元素
if not self.stk2:
while self.stk1:
self.stk2.append(self.stk1.pop())
return self.stk2[-1]
def pop(self) -> int:
self.peek()
return self.stk2.pop()
def empty(self) -> bool:
return not self.stk1 and not self.stk2
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
总结
总的来说,这个方法利用了栈的先进后出的特性,通过两个栈的配合实现了队列的先进先出的功能。
225. 用队列实现栈
题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x)将元素 x 压入栈顶。int pop()移除并返回栈顶元素。int top()返回栈顶元素。boolean empty()如果栈是空的,返回true;否则,返回false。
解题思路
使用队列模拟栈时,每次pop需要将左边n-1个元素挪到队尾,然后把栈顶元素弹出。
- 时间复杂度:
push:O(1)pop:O(n)(需要移动 n-1 个元素)top:O(1)empty:O(1)
- 空间复杂度:O()
- O(n),使用了个队列来存储元素。
from collections import deque
class MyStack:
def __init__(self):
self.queue = deque()
self.top_elem = 0
def push(self, x: int) -> None:
''' 插入元素,更新栈顶 '''
self.queue.append(x)
self.top_elem = x
def pop(self) -> int:
''' 删除元素,需要把队首元素放到队尾,记录倒数第二个作为新的栈顶,删除最后一个元素 '''
size = len(self.queue)
while size > 2:
self.queue.append(self.queue.popleft())
size -= 1
# 倒数第二个元素是新的栈顶
self.top_elem = self.queue[0]
self.queue.append(self.queue.popleft())
# 取出栈顶(队尾元素)
return self.queue.popleft()
def top(self) -> int:
return self.top_elem
def empty(self) -> bool:
return not self.queue
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
总结
利用队列的先进先出特性来模拟栈的后进先出行为。虽然 pop() 操作的时间复杂度较高,但其他操作都是 O(1) 的,整体上实现了栈的功能。需要理解pop时候先将n-2个元素挪到队尾,然后记录n-1作为新的栈顶元素并挪到队尾,最后删除并返回第n个栈顶元素。
20. 有效的括号
题目:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
解题思路
左括号入栈,右括号就出栈匹配,遇到一个不匹配返回false,如果最后栈中左括号都空了就返回false。
- 时间复杂度:O(n)
- 空间复杂度:O(n) - 在最坏情况下,栈中可能会存储所有的左括号。
class Solution:
def isValid(self, s: str) -> bool:
if len(s) <= 1:
return False
stk = []
for i in range(len(s)):
# 如果左括号就入栈
c = s[i]
if c=="(" or c=="[" or c=="{":
stk.append(c)
# 右括号就和最近的左括号匹配
else:
# 匹配上,弹出匹配的左括号
if stk and self.leftOf(c) == stk[-1]:
stk.pop()
# 匹配不上,返回false
else:
return False
# 栈里的左括号是否位空
return not stk
def leftOf(self, c:str)->bool:
if c == ")": return "("
if c == "]": return "["
return "{"
总结
本题是栈的经典应用,通过利用栈的先进后出特性,我们能够高效地判断括号字符串的有效性,确保每个左括号都有对应的右括号并且顺序正确。
1047. 删除字符串中的所有相邻重复项
题目:给出由小写字母组成的字符串 s,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 s 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
解题思路
使用栈记录非重复元素,如果栈为空或者当前元素与栈顶元素不相同就入栈,相同就消消乐pop栈顶并且处理下一个元素。最终把栈中相邻不重复的元素逐个取出然后反转就得到最终结果。
注意是需要重复删除相邻重复的元素,且每次消除两个。
- 时间复杂度:O(n)
- 空间复杂度:O(n),在最坏情况下,栈中可能会存储所有字符。
class Solution:
def removeDuplicates(self, s: str) -> str:
if len(s)<=1: return s
stk = [] # 记录非重复元素的栈
for i in range(len(s)):
c = s[i]
# 栈为空或者和栈顶元素不重复
if not stk or stk[-1] != c:
stk.append(c)
# 栈顶重复,删除栈顶
else:
stk.pop()
return "".join(stk)
总结
栈非常适合处理类似于“消除游戏”中的相邻重复元素问题,因为它能够有效地追踪当前元素与前一个元素之间的关系,从而实现快速的删除操作。