数据结构与算法之栈、队列、优先队列

149 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情

作者: 千石
支持:点赞、收藏、评论
欢迎各位在评论区交流

本文大纲:

栈.png

前言

题目练习步骤:

  1. 给自己10分钟,读题并思考解题思路
  2. 有了思路以后开始写代码,如果在上一步骤中没有思路则停止思考并且看该题题解
  3. 在看懂题解(暂时没看懂也没关系)的思路后,背诵默写题解,直至能熟练写出来
  4. 隔一段时间,再次尝试写这道题目

一些解题思路

栈:

栈是一种后进先出的数据结构,常用于模拟系统的调用栈,以及简化递归问题的求解。
在解题中,栈可以用于模拟基于栈的算法,例如括号匹配、表达式求值等。
同时,栈也可以在求解回溯问题(例如八皇后问题)时作为辅助数据结构。

队列:

队列是一种先进先出的数据结构,常用于模拟系统中的队列,例如打印队列等。
在解题中,队列可以用于模拟广度优先搜索(BFS),以及维护一些具有先后顺序的任务,例如动态规划问题的求解。

优先队列:

优先队列是一种具有优先级的队列,在出队时,具有最高优先级的数据会优先出队。
在解题中,优先队列常常被用于解决最短路径问题(例如Dijkstra算法)和动态选择问题(例如贪心算法)。

实战

题目一:20. 有效的括号

image.png

解题思路

  1. 暴力

不断地使用 python 内置的 replace 函数来匹配括号,直到不再有任何括号为止 1. 初始化一个字符串,用于存储有效的括号序列。 2. 使用 replace 函数,将匹配的括号对替换为空字符串。 3. 重复步骤 2,直到不再有任何括号为止。 4. 最后,判断字符串是否为空,若为空,则说明括号序列有效;否则,说明括号序列无效。

复杂度分析:

  • 时间复杂度:
    • 每次执行 replace 函数的时间复杂度为 O(n)O(n),其中 nn 为字符串的长度。因此,最坏情况下,整个算法的时间复杂度为 O(n2)O(n^2)
  • 空间复杂度:
    • 每次执行 replace 函数,都会创建一个新字符串,因此空间复杂度为 O(n)O(n)

执行结果:

image.png

从左到右扫描字符串,并在栈中模拟括号的匹配过程。 1. 创建一个栈,用于存储遇到的左括号。 2. 遍历字符串中的每个字符,如果是左括号,则将其压入栈中。如果是右括号,则从栈中弹出一个左括号,并判断它们是否匹配。 3. 如果在遍历完整个字符串之后,栈仍有元素,则说明括号序列无效。 4. 反之,如果栈为空,说明括号序列有效。 复杂度分析:

  • 时间复杂度:
    • 在最坏情况下,每个括号都要入栈和出栈一次,因此整个算法的时间复杂度为 O(n)O(n),其中 nn 为字符串的长度。
  • 空间复杂度:
    • 空间复杂度为 O(n)O(n),其中 nn 为字符串的长度。因为栈只需要存储最多 nn 个元素。

执行结果:

image.png

代码实现

  1. 暴力
def isValid(s: str) -> bool:
    while '()' in s or '[]' in s or '{}' in s:
        s = s.replace('()', '').replace('[]', '').replace('{}', '')
    return not s
def isValid(s: str) -> bool:
    stack = []
    mapping = {')': '(', ']': '[', '}': '{'}
    for char in s:
        if char in mapping:
            if not stack or stack.pop() != mapping[char]:
                return False
        else:
            stack.append(char)
    return not stack

题目二:84. 柱状图中最大的矩形

image.png

解题思路

  1. 暴力(会超时)

对于每一列柱子,向两边扩展,直到遇到比当前柱子低的柱子为止,计算以该柱子为高的矩形面积,并在所有柱子中取最大值。

复杂度分析:

  • 时间复杂度:

    • 对于每个柱子,需要向两边扩展,复杂度为O(n^2)。
  • 空间复杂度:

    • 只需要常数空间,复杂度为O(1)。

思路应该没问题,不过超时了

image.png
  1. 优化的暴力

从左到右依次枚举每个柱子,计算它作为矩形最短边向左和右扩展的最大长度,与它的高度相乘即可得到最大面积。

如果每个柱子都这样计算一遍,时间复杂度为 O(n2)O(n^2) ,可以通过使用一个单调栈来优化,使得时间复杂度为 O(n)O(n)

复杂度分析:

  • 时间复杂度: O(n)O(n)
  • 空间复杂度: O(n)O(n)

执行结果:

image.png

栈可以帮助维护从栈底到栈顶的高度单调递增的柱子序列,当前柱子高度不断递减时,弹出栈顶元素,并计算以该柱子为高的矩形面积,最后栈中元素仍未弹出时计算面积

复杂度分析:

  • 时间复杂度:

    • 对于每个柱子,只会被压入栈一次且弹出一次,复杂度为O(n)。
  • 空间复杂度:

    • 需要一个栈维护高度单调递增的柱子序列,复杂度为O(n)。

执行结果:

image.png

代码实现

  1. 暴力(会超时)
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        max_area = 0
        for i in range(n):
            left = i
            right = i
            while left > 0 and heights[left - 1] >= heights[i]:
                left -= 1
            while right < n - 1 and heights[right + 1] >= heights[i]:
                right += 1
            max_area = max(max_area, (right - left + 1) * heights[i])
        return max_area
  1. 优化的暴力
def largestRectangleArea(heights):
    n = len(heights)
    left, right = [0] * n, [0] * n
    mono_stack = []
    for i in range(n):
        while mono_stack and heights[mono_stack[-1]] >= heights[i]:
            mono_stack.pop()
        if not mono_stack:
            left[i] = 0
        else:
            left[i] = mono_stack[-1] + 1
        mono_stack.append(i)
    mono_stack = []
    for i in range(n - 1, -1, -1):
        while mono_stack and heights[mono_stack[-1]] >= heights[i]:
            mono_stack.pop()
        if not mono_stack:
            right[i] = n - 1
        else:
            right[i] = mono_stack[-1] - 1
        mono_stack.append(i)
    res = 0
    for i in range(n):
        res = max(res, (right[i] - left[i] + 1) * heights[i])
    return res
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        stack = [-1]
        max_area = 0
        for i in range(n):
            while stack[-1] != -1 and heights[stack[-1]] >= heights[i]:
                height = heights[stack.pop()]
                width = i - stack[-1] - 1
                max_area = max(max_area, height * width)
            stack.append(i)
        while stack[-1] != -1:
            height = heights[stack.pop()]
            width = n - stack[-1] - 1
            max_area = max(max_area, height * width)
        return max_area

总结

本文的主要内容是简单总结了栈、队列和优先队列在解决算法题目中的应用技巧,同时给出了这些数据结构在解决一些算法题目时的思路和代码实现。