算法学习 Day09 栈和队列1

146 阅读6分钟

本文参考代码随想录提供的教程:栈与队列

栈和队列是编程中的核心数据结构,它们分别以先进后出和先进先出的特性,为算法设计和系统架构提供了基础支持。栈的特性在于它只能从一端进行数据的添加和移除操作,这使得它在处理递归、回溯和函数调用等场景中显得尤为重要。而队列则允许从一端添加数据,从另一端移除数据,这种结构在任务调度、缓冲处理等场合中发挥着关键作用。这两种结构虽然简单,但在实际应用中却有着广泛的用途和深远的影响。

232. 用栈实现队列

文章讲解

视频讲解

题目:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 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)的栈,并支持普通栈的全部四种操作(pushtoppop 和 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 ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

解题思路

左括号入栈,右括号就出栈匹配,遇到一个不匹配返回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)
        

总结

栈非常适合处理类似于“消除游戏”中的相邻重复元素问题,因为它能够有效地追踪当前元素与前一个元素之间的关系,从而实现快速的删除操作。