【科学刷题】完全吃透所有栈相关的算法题

https://leetcode-cn.com/tag/stack/problemset/

文章目录

  • 1 常规的栈相关题目
    • 1.1 括号匹配
      • 1.1.1 有效的括号
      • 1.1.2 移除无效的括号
    • 1.2 基于栈的模拟题
      • 1.2.1 原子的数量
      • 1.2.2 反转每对括号间的子串
      • 1.2.3 括号的分数
      • 1.2.4 字符串解码
    • 1.3 栈的压入、弹出序列
    • 1.4 表达式求值
      • 1.4.1 逆波兰表达式求值
      • 1.4.2 基本计算器
        • 1.4.2.1 加减号 + 小括号
        • 1.4.2.2 加减号 + 乘除号 + 小括号
    • 1.5 删除字符串中的所有相邻重复项
  • 2 DP + 栈
    • 2.1 最长有效括号
  • 3 栈的排序
  • 4 最小/最大 --- 栈/队列
    • 4.1 最小栈(常数时间求minOfStack)
    • 4.2 最大栈(常数时间求maxOfStack + popMax)
    • 4.3 队列的最大值
  • 5 单调栈 / 单调队列
    • 5.1 单调栈
      • 5.1.1 柱状图中的最大矩形 --- 从2次遍历到1次遍历,从1维到2维
        • 5.1.1.1 柱状图中最大的矩形
        • 5.1.1.2 [01矩阵]中的 最大矩形
      • 5.1.3 下一个更大元素 --- 从线形到环形
        • 5.1.3.1 【线形】下一个更大元素
        • 5.1.3.2 【环形】下一个更大元素
      • 5.1.4 求[长为K]字典序最小(最大)的子序列 --- 从1维到2维
        • 5.1.4.1 保留长度为N-K的子序列
        • 5.1.4.2 拼接最大数
      • 5.1.5 不同字符的最小子序列
      • 5.1.6 股票价格跨度
    • 5.2 单调队列
      • 5.2.1 队列的最大值
      • 5.2.2 滑动窗口最大值
  • 6 栈与队列的相互转换
    • 6.1 两个栈实现队列
    • 6.2 队列实现栈

1 常规的栈相关题目

1.1 括号匹配

1.1.1 有效的括号

20. 有效的括号

微软一面考过这题

扫描,左括号入栈,右括号与栈顶元素比较,如果能匹配上就消掉

class Solution:
    def isValid(self, s: str) -> bool:
        stack=collections.deque()
        mapper={
            ')':'(',
            '}':'{',
            ']':'[',
        }
        for c in s:
            if c in mapper:
                if not stack:
                    return False
                if mapper[c]!=stack.pop():
                    return False
            else:
                stack.append(c)
        return not stack # 这里写错

1.1.2 移除无效的括号

1249. 移除无效的括号

无效括号分两种情况:

  1. 多余的):如果栈顶出现),说明多余了
  2. 多余的(:如果迭代完成后stack非空,说明(多余了

基于这两点认识,我们可以写以下代码:

class Solution:
    def minRemoveToMakeValid(self, s: str) -> str:
        stack = []
        s = list(s)
        for i, c in enumerate(s):
            if c == "(":
                stack.append(i)
            elif c == ")":
                if stack:
                    stack.pop()
                else:
                    s[i] = ''
        for i in stack:
            s[i] = ''
        return ''.join(s)

1.2 基于栈的模拟题

这类题目都有一个共同的特点:

  1. 需要用一个栈来模拟题设的操作(一般来说是括号匹配之类的),并且这个栈通常会有一个初始元素,表示root或者最顶层的值(对照3,思考为什么)
  2. 遇到左括号,会初始化一个元素,然后push到栈顶
  3. 遇到右括号,会pop栈顶元素,在做一些必要的操作后,更新到此时的栈顶元素(注意不是刚刚pop的栈顶元素
  4. 遇到其他字符,会对栈顶元素进行操作

1.2.1 原子的数量

726. 原子的数量

扫描每个字符:

  • 如果是左括号,则需要新开一个计数器去记录当前括号内的原子数,将计数器push到栈顶
  • 如果是右括号,弹出栈顶计数器top,然后扫描出括号右边的原子数,用这个原子数对top计数器进行更新,合并到栈顶计数器。
  • 如果是其他字符,解析出原子-原子数的KV对,并对栈顶的计数器进行更新
class Solution:
    def countOfAtoms(self, formula: str):
        N = len(formula)
        stack = [Counter()]
        i = 0
        while i < N:
            if formula[i] == "(":
                stack.append(Counter())
                i += 1
            elif formula[i] == ")":
                top = stack.pop()
                i += 1
                i_start = i
                while i < N and formula[i].isdigit():
                    i += 1
                multi = int(formula[i_start:i] or 1)
                for name, cnt in top.items():
                    stack[-1][name] += cnt * multi
            else:
                i_start = i
                i += 1
                while i < N and formula[i].islower():
                    i += 1
                name = formula[i_start:i]
                i_start = i
                while i < N and formula[i].isdigit():
                    i += 1
                multi = int(formula[i_start:i] or 1)
                stack[-1][name] += multi
        return "".join(name + (str(stack[-1][name]) if stack[-1][name] > 1 else '')
                       for name in sorted(stack[-1]))

1.2.2 反转每对括号间的子串

1190. 反转每对括号间的子串

class Solution:
    def reverseParentheses(self, s: str) -> str:
        stack = [""]
        for chr in s:
            if chr == "(":
                stack.append("")
            elif chr == ")":
                top = stack.pop()
                stack[-1] += top[::-1]
            else:
                stack[-1] += chr
        return stack[-1]

1.2.3 括号的分数

856. 括号的分数

一道机器学习岗位面试题:求圆上任取三个点组成锐角三角形的概率

class Solution:
    def scoreOfParentheses(self, S: str) -> int:
        stack = [0]
        for c in S:
            if c == "(":
                stack.append(0)
            else:
                val = stack.pop()
                if val == 0: # 可以视为“叶子结点”
                    delta = 1
                else:       # 可以视为“非叶子结点”
                    delta = val * 2
                stack[-1] += delta
        return stack[0]

1.2.4 字符串解码

394. 字符串解码

class Solution:
    def decodeString(self, s: str) -> str:
        stack = [["", 0]]
        for c in s:
            if c.isdigit():
                stack[-1][1] = stack[-1][1] * 10 + int(c)
            elif c == "[":
                stack.append(["", 0])
            elif c == "]":
                string, _ = stack.pop()
                times = stack[-1][1]
                if times == 0:
                    times = 1
                stack[-1][0] += string * times
                stack[-1][1] = 0
            else:
                stack[-1][0] += c
        return stack[0][0]

1.3 栈的压入、弹出序列

剑指 Offer 31. 栈的压入、弹出序列

946. 验证栈序列

面试题31. 栈的压入、弹出序列(模拟,清晰图解)

好家伙,看了题解才知道我的思路是反过来的,居然还能AC

class Solution:
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        stack = []
        i = 0
        for num in pushed:
            stack.append(num)
            while stack and stack[-1] == popped[i]:
                stack.pop()
                i += 1
        return not stack

1.4 表达式求值

1.4.1 逆波兰表达式求值

150. 逆波兰表达式求值

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        token2op = {
            '*': mul,
            '-': sub,
            '+': add,
            '/': lambda x, y: int(x / y)
        }
        stack = []
        for token in tokens:
            try:
                num = int(token)
            except:
                y = stack.pop()  # 注意顺序是反过来的
                x = stack.pop()
                num = token2op[token](x, y)
            finally:
                stack.append(num)
        return stack[0]

1.4.2 基本计算器

1.4.2.1 加减号 + 小括号

224. 基本计算器

如何想到用「栈」?思路来自于递归

只有加减号 + 小括号的话,边界条件比较简单,我们可以采用1.2 基于栈的模拟题的思路,栈中元素为一个tuple,下标0表示sign,下标1表示res结果

这种思路基本正确,只需要微调就可以用在有 乘除号 的情况中了

class Solution:
    def calculate(self, s: str) -> int:
        # sign, res, num
        stack = [[1, 0, 0]]

        def update_num_to_res(stack):
        	# res += num * sign
            stack[-1][1] += stack[-1][2] * stack[-1][0]
            stack[-1][2] = 0

        for c in s:
            if c == "(":
                stack.append([1, 0, 0])
            elif c == ")":
                update_num_to_res(stack) # 括号闭合时更新
                _, res, _ = stack.pop()
                stack[-1][2] = res # 当前括号中算出的res更新到栈顶的num中
            elif c in ("-", "+"):
                update_num_to_res(stack) # 遇到符号时更新
                stack[-1][0] = 1 if c == "+" else -1
            elif c.isdigit():
                num = stack[-1][2]
                num = num * 10 + int(c)
                stack[-1][2] = num
        update_num_to_res(stack)  # 根节点解析结束时更新
        return stack[0][1]

1.4.2.2 加减号 + 乘除号 + 小括号

772. 基本计算器 III

注:1+2*-3无法通过

拆解复杂问题:实现计算器

做了两个修改:

  1. sign → \rightarrow op (operator)
  2. res → \rightarrow num_stack (栈里面套栈,用来解决优先级的问题)
  3. return res → \rightarrow return sum(num_stack)
class Solution:
    def calculate(self, s: str) -> int:
        # op, num_stack, num
        stack = [['+', [], 0]]
        operators = ['+', '-', '*', '/']

        def update_num_to_res(stack):
            # num_stack 对象引用,不用显式更新
            op, num_stack, num = stack[-1]
            if op == '+':
                num_stack.append(num)
            elif op == '-':
                num_stack.append(-num)
            elif op in ('*', '/'):
                x = num_stack.pop()
                y = num
                if op == '*':
                    num_stack.append(x * y)
                else:
                    num_stack.append(int(x / y))
            # num需要显示更新,置为0
            stack[-1][2] = 0

        for c in s:
            if c == "(":
                # op, num_stack, num
                stack.append(['+', [], 0])
            elif c == ")":
                update_num_to_res(stack)  # 括号闭合时更新
                _, res, _ = stack.pop()
                stack[-1][2] = sum(res)  # 当前括号中算出的res更新到栈顶的num中
            elif c in operators:
                update_num_to_res(stack)  # 遇到符号时更新
                stack[-1][0] = c
            elif c.isdigit():
                num = stack[-1][2]
                num = num * 10 + int(c)
                stack[-1][2] = num
        update_num_to_res(stack)  # 根节点解析结束时更新
        return sum(stack[0][1])

1.5 删除字符串中的所有相邻重复项

1047. 删除字符串中的所有相邻重复项

class Solution:
    def removeDuplicates(self, s: str) -> str:
        stack = []
        for c in s:
            if stack and c == stack[-1]:
                stack.pop()
            else:
                stack.append(c)
        return "".join(stack)

1209. 删除字符串中的所有相邻重复项 II

class Solution:
    def removeDuplicates(self, s: str, k: int) -> str:
        stack = [1]
        n = len(s)
        res = s[0]
        for i in range(1, n):
            res += s[i]
            if len(res) >= 2 and res[-1] == res[-2]:
                stack[-1] += 1
            else:
                stack.append(1)
            if stack[-1] == k:
                stack.pop()
                res = res[:-k]
        return res

2 DP + 栈

2.1 最长有效括号

32. 最长有效括号

  • DP
class Solution:
    def longestValidParentheses(self, s: str) -> int:
        n = len(s)
        dp = [0] * n
        ans = 0
        for i in range(n):
            if i > 0 and s[i] == ')':
                if s[i - 1] == "(" and i - 2 >= 0:
                    dp[i] = dp[i - 2] + 2
                else:
                    pre_len = dp[i - 1]
                    match_ix = i - pre_len - 1
                    if match_ix >= 0 and s[match_ix] == '(':
                        dp[i] = dp[i - 1] + 2
                        if match_ix - 1 >= 0:
                            dp[i] += dp[match_ix - 1]
                ans = max(ans, dp[i])
        return ans

32. 最长有效括号,辅助栈

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        stack = [-1]
        res = 0
        for i, chr in enumerate(s):
            if len(stack) > 1 and chr == ")" and s[stack[-1]] == "(":
                stack.pop()
                res = max(res, i - stack[-1])
            else:
                stack.append(i)
        return res

3 栈的排序

面试题 03.05. 栈排序

猿辅导、美团、字节面试题——双栈排序

要求stack2(辅助栈)从栈底栈顶单调递增

def stackSort(stack1):
    stack2 = []
    while stack1:
        # 取出 【栈1】 的 【栈顶元素】
        top = stack1.pop()
        # 不满足【单调递增】的条件
        # 看起来【stack2】从栈底到栈顶是【单调递减】的
        while stack2 and stack2[-1] > top:
            t = stack2.pop()
            stack1.append(t) # pop 掉这个元素,扔 stack1 中
        # 将 【栈1】 的 【栈顶元素】 push到【栈2】
        stack2.append(top)
    return stack2

4 最小/最大 — 栈/队列

4.1 最小栈(常数时间求minOfStack)

155. 最小栈

剑指 Offer 30. 包含min函数的栈

【科学刷题】完全吃透所有栈相关的算法题_第1张图片

最小栈 (辅助栈法,清晰图解)

时间复杂度 O ( 1 ) O(1) O(1),空间复杂度 O ( N ) O(N) O(N)

维护一个单调递减的辅助栈B:

  • 入栈:如果元素x满足在B上单调递减,入栈
  • 出栈:如果A出栈元素等于B栈顶元素,出栈

因为栈是访问受限的线性数据结构,即只能从栈顶访问栈。所以只能访问栈顶

如果这题改为最小队列(常数时间求minOfQueue),应该仿照单调栈的做法,维护一个单调递增双端队列,这样如果一个比当前元素都要小的元素(新的min-value)入队后,会从队尾将队列中所有元素pop掉,min-value成为新的队头

class MinStack:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.A = []
        self.B = []


    def push(self, x: int) -> None:
    	# 栈A正常push
        self.A.append(x)
        # 如果栈B元素空,或者x小于栈B的栈顶,push到栈B
        # 目的是维护栈B为一个单调递减(从栈底到栈顶)的栈
        if (not self.B) or x <= self.B[-1]:
            self.B.append(x)


    def pop(self) -> None:
    	# 栈A正常出栈
        top = self.A.pop()
        # 如果A的出栈元素恰为栈B的栈顶元素,B也出栈
        if self.B and top == self.B[-1]:
            self.B.pop()

	# A B 的peak函数正常执行
    def top(self) -> int:
        return self.A[-1]


    def getMin(self) -> int:
        return self.B[-1]

4.2 最大栈(常数时间求maxOfStack + popMax)

716. 最大栈

  • 双栈法

因为popMax的存在,这个操作需要不断出栈直到找到最大元素,然后再push回来,时间复杂度为 O ( N ) O(N) O(N)

class MaxStack(list):
    def push(self, x):
        m = max(x, self[-1][1] if self else None)
        self.append((x, m))

    def pop(self):
        return list.pop(self)[0]

    def top(self):
        return self[-1][0]

    def peekMax(self):
        return self[-1][1]

    def popMax(self):
        m = self[-1][1]
        b = []
        while self[-1][0] != m:
            b.append(self.pop())

        self.pop()
        map(self.push, reversed(b))
        return m
  • 双向链表 + 平衡树

【科学刷题】完全吃透所有栈相关的算法题_第2张图片
TODO: 用C++实现

4.3 队列的最大值

剑指 Offer 59 - II. 队列的最大值

如果是最小队列,应该用单调栈的方法维护一个单调递增的双端队列。在4.1阐述过

如果是最大队列,应该用单调栈的方法维护一个单调递减的双端队列。本题就是这个思路

class MaxQueue:

    def __init__(self):
        self.queue = collections.deque()
        # monotonous queue
        self.monoq = collections.deque()


    def max_value(self) -> int:
        return self.monoq[0] if self.monoq else -1


    def push_back(self, value: int) -> None:
        # 单调递减队列 (前一个元素大于等于后一个元素)
        # fixme: 老是把if 写成 while
        # 相当于:  not self.monoq[-1] >= value 即不满足单调递减的条件
        while self.monoq and self.monoq[-1] < value:
            self.monoq.pop()
        self.monoq.append(value)
        self.queue.append(value)


    def pop_front(self) -> int:
        if not self.queue:
            return -1
        left = self.queue.popleft()
        if self.monoq[0] == left:
            self.monoq.popleft()
        return left

5 单调栈 / 单调队列

5.1 单调栈

5.1.1 柱状图中的最大矩形 — 从2次遍历到1次遍历,从1维到2维

5.1.1.1 柱状图中最大的矩形

84. 柱状图中最大的矩形

【科学刷题】完全吃透所有栈相关的算法题_第3张图片对于每个元素,往左找到第一个小于这个元素的下标,往右找到第一个小于这个元素的下标

从左往右,维护一个单调递增栈,这样当前元素入栈,栈中所有大于当前元素元素pop掉了,只剩下小于当前元素最近元素

所以我们可以做两个栈出来:

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        # left -> right
        stack = []
        left = []
        for i in range(n):
            # 不满足【严格单调递增】
            while stack and (not heights[stack[-1]] < heights[i]):
                stack.pop()
            left.append(stack[-1] if stack else -1)
            stack.append(i)
        print(left)
        # right -> left
        stack = []
        right = [n for i in range(n)]
        for i in range(n - 1, -1, -1):
            while stack and (not heights[stack[-1]] < heights[i]):
                stack.pop()
            # 用append就错啦
            right[i] = (stack[-1] if stack else n)
            stack.append(i)
        print(right)
        # calc
        ans = 0
        for i in range(n):
            ans = max(ans, (right[i] - left[i] - 1) * heights[i])
        return ans

我们可以将2个单调栈 + 2次遍历 化简为 1个单调栈 + 1次遍历

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        # left -> right
        stack = []
        left = [-1 for i in range(n)]
        right = [n for i in range(n)]
        for i in range(n):
            # 不满足【严格单调递增】
            while stack and (not heights[stack[-1]] < heights[i]):
                right[stack.pop()] = i
            left[i] = (stack[-1] if stack else -1)
            stack.append(i)
        ans = 0
        for i in range(n):
            ans = max(ans, (right[i] - left[i] - 1) * heights[i])
        return ans

5.1.1.2 [01矩阵]中的 最大矩形

注:[01矩阵]中的 最大正方形 用DP做:
221. 最大正方形

85. 最大矩形

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        N = len(heights)
        left = [0] * N
        right = [N] * N
        stack = []
        for i in range(N):
            while stack and heights[stack[-1]] > heights[i]:
                right[stack.pop()] = i
            left[i] = stack[-1] if stack else -1
            stack.append(i)
        return max(heights[i] * (right[i] - left[i] - 1) for i in range(N))

    def maximalRectangle(self, matrix: List[List[str]]) -> int:
        N = len(matrix)
        if not N:
            return 0
        M = len(matrix[0])
        if not M:
            return 0
        heights = [0] * M
        ans = 0
        for i in range(N):
            for j in range(M):
                if matrix[i][j] == "1":
                    heights[j] += 1
                else:
                    heights[j] = 0
            ans = max(ans, self.largestRectangleArea(heights))
        return ans

5.1.3 下一个更大元素 — 从线形到环形

5.1.3.1 【线形】下一个更大元素

496. 下一个更大元素 I

5.1.1.1 柱状图中最大的矩形中,我们讨论了:要找到左边第一个更小元素,需要维护一个单调递增栈

而要找下一个最大元素,应该从右往左,维护一个单调递减栈。这样新元素入栈前,会把所以小于新元素的元素pop掉(保证新元素<栈顶元素,从而满足单调递减),此时的栈顶元素就是要找的下一个最大元素

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        nums = nums2
        mp = {}
        # ---------------------
        # 单调递减栈
        stack = []
        L = len(nums)
        ans = [0] * L
        for i in range(L - 1, -1, -1):
            num = nums[i]
            # 不满足【单调递减】
            while stack and (not stack[-1] > num):
                stack.pop()
            ans[i] = stack[-1] if stack else -1
            stack.append(num)
            # 缓存
            mp[num] = ans[i]
        # -----------------------
        return [mp[x] for x in nums1]

739. 每日温度

这题和【下一个更大元素】基本一致

class Solution:
    def dailyTemperatures(self, T: List[int]) -> List[int]:
        nums = T
        # -------------------------
        # 单调递减栈
        stack = []
        L = len(nums)
        ans = [0] * L
        for i in range(L - 1, -1, -1):
            num = nums[i]
            # 不满足【单调递减】
            while stack and (not nums[stack[-1]] > num):
                stack.pop()
            ans[i] = stack[-1] - i if stack else 0
            stack.append(i)
        return ans

5.1.3.2 【环形】下一个更大元素

难度升级为【环形】的题目还有:
打家劫舍2
环形的【跳台阶】,一个字节面试题
环形取石子,区间DP

503. 下一个更大元素 II

【科学刷题】完全吃透所有栈相关的算法题_第4张图片

[1,  2,  1,  1,  2,  1]
[2, -1,  2 , 2 ,-1 ,-1]

从右往左,覆盖老元素

class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        stack = []
        L = len(nums)
        ans = [0] * L
        for i in range(L * 2 - 1, -1, -1):
            num = nums[i % L]
            while stack and (not stack[-1] > num):
                stack.pop()
            ans[i % L] = stack[-1] if stack else -1
            stack.append(num)
        return ans

5.1.4 求[长为K]字典序最小(最大)的子序列 — 从1维到2维

这种类型的题就比5.1.15.1.2 这样的题更简单,或者说更直白一些:

  1. 如果需要得到一个字典序最小的子序列,维护单调递增栈,(即最小的元素放在最前面,字典序最小)
  2. 如果需要得到一个字典序最大的子序列,维护单调递减

5.1.4.1 保留长度为N-K的子序列

402. 移掉K位数字

移除这个数中的 k 位数字,使得剩下的数字最小

首先要理解题意, 求 N − K N-K NK个最小的数

思维转变, 把丢弃视为保留

删除第一个不单调递增(开始下降, num[x] < num[x-1]

class Solution:
    def removeKdigits(self, num: str, k: int) -> str:
    	# 单调递增栈
        numStack = []
        for digit in num:
        	# 相比普通单调栈,多个k这个判断条件
        	#                        不满足于【单调递增】
            while k and numStack and (not numStack[-1] <= digit):
                numStack.pop()
                k -= 1 # 最多只能pop k个,等同于保留N-K个
            numStack.append(digit)
        # 特殊情况: 单调递增的num
        # [:-k]  等同于删除末尾的k个数字
        finalStack = numStack[:-k] if k else numStack
        # 特殊情况:前导0
        #  or "0" 这步相当妙,默写的时候没默出来
        return "".join(finalStack).lstrip("0") or "0"

5.1.4.2 拼接最大数

321. 拼接最大数

出现频率较小,有空再研究一下这题

class Solution:
    def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:
        def pick_max(lst, k):
            stack = []
            drop = len(lst) - k  # 没想到
            for e in lst:
                # 没想到
                while drop and stack and stack[-1] < e:
                    stack.pop()
                    drop -= 1  # 没想到
                stack.append(e)
            return stack[:k]  # 没想到截断 (2次)

        def merge(la, lb):
            res = []
            # 为的是解决 [6, 7], [6, 0, 4] 这样的case
            while la or lb:
                # bigger 保证不为空列表
                bigger = la if la > lb else lb
                res.append(bigger.pop(0))  # 简写
                # bigger.pop(0) # 简写
            return res

        ret = []
        for sp in range(k + 1):
            # 判断条件的 <= 写错为 < 
            if sp <= len(nums1) and k - sp <= len(nums2):
                tmp = merge(
                    pick_max(nums1, sp),
                    pick_max(nums2, k - sp),
                )
                ret = max(ret, tmp)
        return ret

5.1.5 不同字符的最小子序列

316. 去除重复字母

1081. 不同字符的最小子序列

counter表示剩余未遍历字符的出现次数

为什么用单调栈做?因为找出来的是子序列

如果去掉counter的代码, 会造成使得每个字母只出现一次的条件失效,即有的字母出现0次
counter的作用是在删字母的时候,判断是否会导致有的字母不出现

class Solution:
    def removeDuplicateLetters(self, s) -> int:
        stack = []
        counter = collections.Counter(s)
        for c in s:
            # 要求去重
            if c not in stack:
                # stack[-1] > c | 【严格单调递增】条件被破坏 
                # 需满足 counter[stack[-1]] > 0 的条件,即栈顶元素pop之后,还有替代的
                while stack and (not stack[-1] < c) and counter[stack[-1]] > 0:
                    stack.pop()
                stack.append(c)
            # 遍历一个字符,做更新
            counter[c] -= 1
        return "".join(stack)

TODO: 理解还不深刻, 继续理解

在上一题中,限值条件是k(所以出现在上一题的while判断条件中),这一题的限值条件是使得每个字母只出现一次,故判断条件是counter[stack[-1]]

counter 表示当前指针及之后所含元素的计数。如果从栈中弹出了元素,并且这个元素后续没有机会再添加进来了,这一定是非法的。

counter[mono_stack.back()] 写成了 counter[c] ,人没了

5.1.6 股票价格跨度

901. 股票价格跨度

class StockSpanner:

    def __init__(self):
        # 维护一个【单调递减】栈
        self.stack = []

    def next(self, price: int) -> int:
        cnt = 1
        #                   不满足 单调递减 
        while self.stack and (not self.stack[-1][0] > price):
            top = self.stack.pop()
            cnt += top[1]
        self.stack.append([price, cnt])
        return cnt

5.2 单调队列

5.2.1 队列的最大值

剑指 Offer 59 - II. 队列的最大值

class MaxQueue:

    def __init__(self):
        self.queue = collections.deque()
        # monotonous queue
        self.monoq = collections.deque()


    def max_value(self) -> int:
        return self.monoq[0] if self.monoq else -1


    def push_back(self, value: int) -> None:
        # 单调递减队列 (前一个元素大于等于后一个元素)
        # fixme: 老是把if 写成 while
        while self.monoq and self.monoq[-1] < value:
            self.monoq.pop()
        self.monoq.append(value)
        self.queue.append(value)


    def pop_front(self) -> int:
        if not self.queue:
            return -1
        left = self.queue.popleft()
        if self.monoq[0] == left:
            self.monoq.popleft()
        return left

5.2.2 滑动窗口最大值

239. 滑动窗口最大值

剑指 Offer 59 - I. 滑动窗口的最大值

  • 题解

labuladong 单调队列解题详解

【Python】 简洁的单调队列解法(详解+注释)

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        queue = collections.deque()
        N = len(nums)
        res = []
        for i in range(N):
            # 满足单调递减
            while queue and nums[queue[-1]] < nums[i]:
                queue.pop()  # 默认右端出栈
            queue.append(i)
            # 删掉左端不在滑动窗口内元素
            if queue[0] <= i - k:
                queue.popleft()
            # 如果窗口已经形成,记录结果
            if i >= k - 1:
                # 结果记录的是最大值,所以需要把索引带入nums (默写出错)
                res.append(nums[queue[0]])
        return res

6 栈与队列的相互转换

6.1 两个栈实现队列

剑指 Offer 09. 用两个栈实现队列

入队,直接push到stack1中即可

stack1:
-------------
| 1 2 3 4 
-------------

出队,如果stack2为空,将stack1全部倒腾到stack2中,然后取stack2的栈顶

stack1:
-------------
| 
-------------
stack2:
-------------
    1 2 3 4 | (原来的栈顶变成栈底)
-------------
class CQueue:

    def __init__(self):
        self.stack1 = []
        self.stack2 = []


    def appendTail(self, value: int) -> None:
        self.stack1.append(value)


    def deleteHead(self) -> int:
        if not self.stack2:
            while self.stack1:
                self.stack2.append(self.stack1.pop())
        if self.stack2:
            return self.stack2.pop()
        return -1

6.2 队列实现栈

225. 用队列实现栈

class MyStack {
    Queue<Integer> queue;

    /** Initialize your data structure here. */
    public MyStack() {
        queue=new LinkedList<>();
    }

    /** Push element x onto stack. */
    public void push(int x) {
        int n = queue.size();
        queue.offer(x);
        for (int i = 0; i < n; i++) {
            queue.offer(queue.poll());
        }
    }

    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        return queue.poll();
    }

    /** Get the top element. */
    public int top() {
        return queue.peek();
    }

    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queue.isEmpty();
    }
}


/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

你可能感兴趣的:(科学刷题,算法,leetcode,python)