算法刷题总结:栈与队列

1 知识点

1.1 栈

堆栈(英语:stack)又称为堆叠,是计算机科学中的一种抽象数据类型,只允许在有序的线性数据集合的一端(称为堆栈顶端,英语:top)进行加入数据(英语:push)和移除数据(英语:pop)的运算。因而按照后进先出(LIFO, Last In First Out)的原理运作。

栈常用一维数组或链表来实现。

栈使用两种基本操作:推入(压栈,push)和弹出(弹栈,pop):

  • 推入:将数据放入堆栈顶端,堆栈顶端移到新放入的数据。
  • 弹出:将堆栈顶端数据移除,堆栈顶端移到移除后的下一笔数据。

栈的基本特点:

  1. 先入后出,后入先出。
  2. 除头尾节点之外,每个元素有一个前驱,一个后继。

栈中的每个元素称为一个frame。而最上层元素称为top frame。栈只支持三个操作, pop, top, push。

pop取出栈中最上层元素(8),栈的最上层元素变为早先进入的元素(9)。

top查看栈的最上层元素(8)。

push将一个新的元素(5)放在栈的最上层。

栈不支持其他操作。如果想取出元素12, 必须进行3次pop操作。

栈1

1.2 队列

队列(queue)是一种采用先进先出(FIFO)策略的抽象数据结构,即最先进队列的数据元素,同样要最先出队列。队列跟我们排队买票一样,先来排队的肯定先买票,后来排队的的后买到票。队列如下图所示:

队列1

队列有两个重要的概念,一个叫队头,一个叫队尾,队头指向的是第一个元素,而队尾指向的是最后一个元素。队列跟栈一样也是访问受限制的,所以队列也只有两个主要的操作:入队(enqueue)操作出队(dequeue)操作 。入队操作就是将一个元素添加到队尾,出队操作就是从队头取出一个元素。

队列的底层实现可以用数组和链表,基于数组实现的队列叫作顺序队列,基于链表实现的队列叫作链式队列

队列的操作方式和栈类似,唯一的区别在于队列只允许新数据在后端进行添加。

2 常见面试题

2.1 栈

155. 最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。

class MinStack:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.stack = []
        self.minx = []   # 利用辅助栈来存储最小值

    def push(self, x: int) -> None:
        self.stack.append(x)
        if not self.minx or x <= self.minx[-1]:  # 如果minx没有元素,或者当前x小于等于辅助栈顶元素,则将当前x存入辅助栈顶
            self.minx.append(x)

    def pop(self) -> None:
        if self.stack.pop() == self.minx[-1]: # 先进行stack.pop(),如果去除元素与辅助栈顶元素相同,则辅助栈也去除元素
            self.minx.pop()

    def top(self) -> int:
        return self.stack[-1]  # 返回数据栈顶元素

    def getMin(self) -> int:
        return self.minx[-1]   # 返回辅助栈顶元素


# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()
150. 逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。

有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

思路:利用栈的思想,如果是数字,则压入栈;若为符号,即执行“pop取栈顶 -> pop取新栈顶 -> 计算”操作。

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        symbol = ["+", "-", "*", "/"]
        resList = []
        if len(tokens) == 0:
            return 0
        else:
            for c in tokens:
                if c not in symbol:
                    resList.append(int(c))
                else:
                    b = resList.pop()
                    a = resList.pop()
                    if c == "+":
                        resList.append(a+b)
                    elif c == "-":
                        resList.append(a-b)
                    elif c == "*":
                        resList.append(a*b)
                    elif c == "/":
                        resList.append(int(a/b))
        return resList[0]
394. 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

思路:通过辅助栈进行操作

class Solution:
    def decodeString(self, s: str) -> str:
        stack = []
        res = ""
        multi = 0
        for c in s:
            if "0" <= c <= "9":
                multi = multi*10 + int(c)    # 考虑数字是2位以上的情况
            elif c == "[":
                stack.append([multi, res])   # 当前 multi 和 res 入栈,并分别置空置 00
                multi = 0   # 重置
                res = ""
            elif c == "]":
                cur_multi, last_res = stack.pop()  # 取出[]中的重复倍数和字符串
                res = last_res + cur_multi * res   # 拼接字符串
            else:
                res += c   # 遇到其他,则当字母串处理
        return res

利用栈进行 DFS 递归搜索模板

class Solution:
    def decodeString(self, s: str) -> str:
        def dfs(i):
            res = ""
            multi = 0
            while i < len(s):
                if '0' <= s[i] <= '9':
                    multi = multi * 10 + int(s[i])
                # 遇到'['开始将后续的string递归
                elif s[i] == '[':
                    i, temp = dfs(i + 1)
                    # 注意,返回i的含义是更新上层递归指针位置,因为内层递归已经吃掉一串str,若不跟新i,外层仍然从i+1开始,则会重复处理内层处理过的一串str。
                    res += multi * temp
                    multi = 0
                # 遇到']'到达递归边界,结束递归,返回新i和处理好的内层res
                elif s[i] == ']':
                    return i, res
                else:
                    res += s[i]
                i += 1
            return res
        return dfs(0)
94. 二叉树的中序遍历

给定一个二叉树,返回它的中序 遍历。

思路:通过stack 保存已经访问的元素,用于原路返回

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        stack = []
        while stack or root:
            while root:
                stack.append(root)
                root = root.left
            root = stack.pop()      # 此时左子树遍历完成
            res.append(root.val)    # 将父节点加入列表
            root = root.right       # 遍历右子树
        return res
200. 岛屿数量

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

思路:通过深度搜索遍历可能性(注意标记已访问元素)

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        if not grid:
            return 0
        m, n = len(grid), len(grid[0])
        res = 0
        def backtrack(grid, i, j):
            if i < 0 or i >=m or j < 0 or j >= n or grid[i][j] != '1':   # 判断边界
                return
            grid[i][j] = '0'    # 遍历过的地方都标记为0,避免重复寻找
            backtrack(grid, i, j+1)
            backtrack(grid, i+1, j)
            backtrack(grid, i, j-1)
            backtrack(grid, i-1, j)

        for i in range(m):
            for j in range(n):
                if grid[i][j] == '1':
                    backtrack(grid, i, j)
                    res += 1
        return res
133. 克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 valint) 和其邻居的列表(list[Node])。

思路:遍历整个图,遍历时候要记录已经访问点,我们用一个字典记录。

# DFS 深度优先遍历
class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        lookup = {}
        
        def dfs(node):
            if not node:
                return
            if node in lookup:
                return lookup[node]
            clone = Node(node.val, [])
            lookup[node] = clone
            for i in node.neighbors:
                clone.neighbors.append(dfs(i))
            
            return clone
          
        return dfs(node)
# BFS 广度优先遍历
class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        from collections import deque
        lookup = {}
        
        if not node:
            return
        clone = Node(node.val, [])
        lookup[node] = clone
        queue = deque()
        queue.appendleft(node)
        while queue:
            temp = queue.pop()
            for n in temp.neighbors:
                if n not in lookup:
                    lookup[n] = Node(n.val, [])
                    queue.appendleft(n)
                lookup[temp].neighbors.append(lookup[n])
        return clone
84. 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积

# 优化中心扩展法
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        l, r, ans = [-1] * n, [n] * n, 0
        for i in range(1, n):
            j = i - 1
            while j >= 0 and heights[j] >= heights[i]:
                j = l[j]
            l[i] = j
        for i in range(n - 2, -1, -1):
            j = i + 1
            while j < n and heights[j] >= heights[i]:
                j = r[j]
            r[i] = j
        for i in range(n):
            ans = max(ans, heights[i] * (r[i] - l[i] - 1))
        return ans
# 单调栈
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        """
        头部加0便于处理当栈里只有一个有效元素要弹出的时候计算面积。
        如果头部不加0,最后一个有效元素被弹出的时候,栈已经为空了,则还需要特殊处理。
        尾部加0,如果0最后一个入栈,所有的数都会弹出
        """
        stack = []
        heights = [0] + heights + [0]
        res = 0
        for i in range(len(heights)):
            while stack and heights[stack[-1]] > heights[i]:
                tmp = stack.pop()
                res = max(res, (i - stack[-1] - 1) * heights[tmp])
            stack.append(i)
        return res

2.2 队列

232. 用栈实现队列

使用栈实现队列的下列操作:

push(x) – 将一个元素放入队列的尾部。
pop() – 从队列首部移除元素。
peek() – 返回队列首部的元素。
empty() – 返回队列是否为空。

思路:定义两个栈,一个负责入栈,一个负责出栈,元素先进入入栈,再压入出栈

class MyQueue:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.instack = []
        self.outstack = []

    def push(self, x: int) -> None:
        """
        Push element x to the back of queue.
        """
        self.instack.append(x)

    def pop(self) -> int:
        """
        Removes the element from in front of queue and returns that element.
        """
        if not self.outstack:
            while self.instack:
                self.outstack.append(self.instack.pop())
        return self.outstack.pop()

    def peek(self) -> int:
        """
        Get the front element.
        """
        if not self.outstack:
            while self.instack:
                self.outstack.append(self.instack.pop())
        return self.outstack[-1]

    def empty(self) -> bool:
        """
        Returns whether the queue is empty.
        """
        return not self.instack and not self.outstack
542. 01 矩阵

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

思路:BFS,从0进队列,弹出之后计算上下左右的结果,将上下左右重新进队列进行二层操作

class Solution:
    def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:
        m, n = len(matrix), len(matrix[0])
        q = collections.deque([])
        visited = set()
        # 初始化队列,将所有起始点加入
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == 0:
                    q.append((i, j))
                    visited.add((i, j))
        # 将所有相邻节点加入队列
        while q:
            i, j = q.popleft()
            for x, y in [(i+1, j), (i-1, j), (i, j+1), (i, j-1)]:
                if 0 <= x < m and 0 <= y < n and (x, y) not in visited:
                    matrix[x][y] = matrix[i][j] + 1
                    visited.add((x, y))
                    q.append((x, y))
        return matrix

总结

  • 熟悉栈的使用场景
    • 后入先出,保存临时值
    • 利用栈 DFS 深度搜索
  • 熟悉队列的使用场景
    • 利用队列 BFS 广度搜索

参考资料

  1. 堆栈-维基百科
  2. https://juejin.im/post/6844903928476368909
  3. LeetCode题解
  4. https://greyireland.gitbook.io/algorithm-pattern/shu-ju-jie-gou-pian/stack_queue
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值