栈的基础概念与经典题目(Leetcode题解-Python语言)

本文详细介绍了栈这一数据结构在多种问题中的应用,包括如何使用栈实现队列、最小栈、删除字符串重复项、有效括号判断、基本计算器、逆波兰表达式求值、验证栈序列以及单调栈解决的多个问题。通过实例解析,阐述了栈的后入先出特性在解决实际问题中的关键作用。
摘要由CSDN通过智能技术生成

栈是先入后出后入先出)的数据结构,常用操作就 push 和 pop,Python中用列表实现即可,基本概念可以看Leetbook相关章节

普通栈

232. 用栈实现队列

class MyQueue:

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

    def push(self, x: int) -> None:
        while self.stack2:
            self.stack1.append(self.stack2.pop())
        self.stack1.append(x)
        self.size += 1

    def pop(self) -> int:
        while self.stack1:
            self.stack2.append(self.stack1.pop())
        self.size -= 1
        ans = self.stack2.pop()
        while self.stack2:
            self.stack1.append(self.stack2.pop())
        return ans

    def peek(self) -> int:
        return self.stack1[0]

    def empty(self) -> bool:
        return self.size == 0

两个栈,用 stack1 来存放 push 进来的数,当要 pop 的时候,借助 stack2 把最左边的数 pop 出去(相当于 popleft),然后再把剩余的数放回 stack1,由于需要判断空,所以用一个 size 大小来记录。

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

class MinStack:
    def __init__(self):
        self.stack = [(0, float('+inf'))]

    def push(self, x: int) -> None:
        self.stack.append((x, min(self.stack[-1][1], x)))

    def pop(self) -> None:
        self.stack.pop()

    def top(self) -> int:
        return self.stack[-1][0]

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

实现最小栈的关键就是要用辅助栈记录每次 push 操作时的最小值,这样栈值在递增时,最小值永远是第一个值(如 [1, 2, 3, 4] 对应 [1, 1, 1, 1]);在递减时,最小值永远是当前值(如 [4, 3, 2, 1] 对应 [4, 3, 2, 1])。

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

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

栈的最重要的应用就是匹配,这是由它后入先出的性质所决定的。

20. 有效的括号

class Solution:
    def isValid(self, s: str) -> bool:
        if len(s) % 2 == 1:
            return False
        stack = []
        for ch in s:
            if ch in ('(', '[', '{'):
                stack.append(ch)
            else:
                if len(stack) == 0:
                    return False
                pre = stack.pop()
                if (pre == '(' and ch != ')') or (pre == '[' and ch != ']') or (pre == '{' and ch != '}'):
                    return False
        return len(stack) == 0

首先判断长度,若为奇数必然不能匹配,直接返回 False。然后分类讨论:如果是三个左括号之一,就 push 进入栈;如果是三个右括号之一,就检查栈顶,若栈顶为空就肯定不匹配,不为空则考察弹出的栈顶元素,若不是对应的左括号则返回 False。最后如果还有左括号(栈非空),也返回 False。

227. 基本计算器 II

class Solution:
    def calculate(self, s: str) -> int:
        stack = []
        size = len(s)
        index = 0
        op = '+'
        while index < size:
            if s[index] == ' ':
                index += 1
                continue
            if s[index] in '+-*/':
                op = s[index]
            elif s[index].isdigit():
                num = ord(s[index]) - ord('0')
                while index + 1 < size and s[index+1].isdigit():
                    index += 1
                    num = num * 10 + ord(s[index]) - ord('0')
                if op == '+':
                    stack.append(num)
                elif op == '-':
                    stack.append(-num)                    
                elif op == '*':
                    top = stack.pop()
                    stack.append(top * num)
                elif op == '/':
                    top = stack.pop()
                    stack.append(int(top / num))
            index += 1
        return sum(stack)

这题与上一个的括号题类似,也是分类讨论。如果遇到空格则跳过;如果遇到符号则记录下来;如果遇到数字,则根据其前面的符号来进行相应的运算。这里用 while 循环是因为要处理多位数字的情况,如 42 这种数,在遇到符号前都将其作为一个数(而不是 4 和 2 两个数)。

150. 逆波兰表达式求值

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        size = len(tokens)
        index = 0
        while index < size:
            if tokens[index] in '+-*/':
                second = stack.pop()
                first = stack.pop()
                if tokens[index] == '+':
                    num = first + second
                elif tokens[index] == '-':
                    num = first - second
                elif tokens[index] == '*':
                    num = first * second
                elif tokens[index] == '/':
                    num = int(first / second)
                stack.append(num)
                index += 1
            else:
                stack.append(int(tokens[index]))
                index += 1
        return stack.pop()

仿照上一题的写法,同样是分类讨论,字符是数字的话就入栈,是运算符的话就让两个操作数出栈,进行运算后把结果再入栈。由于这题多位数是直接给出来了的,所以可以用 for 循环而不是 while 循环,如下所示:

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for token in tokens:
            if token == '+':
                stack.append(stack.pop() + stack.pop())
            elif token == '-':
                stack.append(-stack.pop() + stack.pop())
            elif token == '*':
                stack.append(stack.pop() * stack.pop())
            elif token == '/':
                stack.append(int(1/stack.pop()*stack.pop()))
            else:
                stack.append(int(token))
        return stack.pop()

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

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

用指针记录 popped 序列,然后逐个把 pushed 序列的元素 push 进入栈中,如果出现相同值,则弹出栈顶元素,同时指针右移1位,如果所有元素都能pop 则返回 True。

单调栈

496. 下一个更大元素 I

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        ans = []
        stack = []
        num_map = dict()

        for num in nums2:
            while stack and stack[-1] < num:
                num_map[stack[-1]] = num
                stack.pop()
            stack.append(num)
        
        for num in nums1:
            ans.append(num_map.get(num, -1))
        
        return ans

利用单调栈和字典记录下 nums2 中每个元素右边第一个比自己大的元素,然后遍历 nums1 从字典找相应答案即可。

503. 下一个更大元素 II

class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        n = len(nums)
        ans = [-1 for _ in range(n)]
        stack = []
        
        for i in range(n * 2):
            while stack and nums[i % n] > nums[stack[-1]]:
                index = stack.pop()
                ans[index] = nums[i % n]
            stack.append(i % n)

        return ans

通过循环 2n 次可以达到循环数组的效果,但毕竟答案要的索引是 n 个,所以通过对 n 取余来表示原数组的下标。

739. 每日温度

class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        n = len(temperatures)
        ans = [0 for _ in range(n)]
        stack = []

        for i in range(n):
            while stack and temperatures[stack[-1]] < temperatures[i]:
                index = stack.pop()
                ans[index] = i - index
            stack.append(i)
        
        return ans

遍历每一天的温度,用一个栈记录每天的温度下标,当遍历到第 i 天时,比较第 i 天是否大于前面某天的温度,如果是则弹出该天(已找到答案),并在 ans 数组中记录下天数(差值)。

316. 去除重复字母1081. 不同字符的最小子序列

class Solution:
    def removeDuplicateLetters(self, s: str) -> str:
        stack = []
        counter = collections.Counter(s)

        for ch in s:
            if ch not in stack:
                while stack and ch < stack[-1] and counter[stack[-1]] > 0:
                    stack.pop()
                stack.append(ch)
            counter[ch] -= 1

        return ''.join(stack)

首先用字典记录下每个字符出现的次数,然后从左往右遍历字符串。对于每一个字符,如果它已经在单调递减栈里面了,则不需要入栈,直接次数减 1;如果它不在栈里,则判断栈顶元素是否可以出栈(比当前字符大且不是剩下的唯一字符),之后再将字符入栈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值