Task03:栈

本文介绍了栈在处理有效括号、逆波兰表达式求值问题上的应用,以及如何在常数时间内解决这些挑战。通过使用栈结构,我们可以有效地检查括号匹配,计算中缀表达式的值,并在处理含退格字符串时保持效率。此外,还探讨了在实际编程中如何优化这些算法,例如使用字典简化代码,以及在原数组上进行操作以节省空间。
摘要由CSDN通过智能技术生成

1.视频题目

1.1 有效的括号

1.1.1 描述

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例 1:

输入:s = “()”
输出:true

示例 2:

输入:s = “()[]{}”
输出:true

示例 3:

输入:s = “(]”
输出:false

示例 4:

输入:s = “([)]”
输出:false

示例 5:

输入:s = “{[]}”
输出:true

提示:

1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成

1.1.2 代码

顺序扫描,对于左括号直接压入栈,对于右括号则取栈顶匹配

可以叠加if去判断是否匹配,也可以使用字典

class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        pairs = {
                    ')':'(', 
                    '}':'{',
                    ']':'['
                }
        for i in s :
            if i=='(' or i =='[' or i =='{' :
                stack.append(i)
            else :
                if len(stack) != 0 and stack[-1] == pairs[i] :
                    stack.pop()
                else :
                    return False
        if len(stack) == 0:
            return True
        else :
            return False
1.1.3 总结

字典便于进行匹配,同时也简化了代码

1.2 逆波兰表达式求值

1.2.1 描述

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

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

注意 两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例 1:

输入:tokens = [“2”,“1”,"+",“3”,"*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:

输入:tokens = [“4”,“13”,“5”,"/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:

输入:tokens = [“10”,“6”,“9”,“3”,"+","-11","","/","",“17”,"+",“5”,"+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

提示:

1 <= tokens.length <= 104
tokens[i] 是一个算符("+"、"-"、"*" 或 “/”),或是在范围 [-200, 200] 内的一个整数

逆波兰表达式:

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:

去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

1.2.2 代码

比较好的是表达式是可以直接按顺序计算的,而不用考虑运算符的优先顺序问题

所以说就是把数字压入栈中,然后遇到运算符就弹出两个数字运算,结果再压入栈

要注意数字在运算符两边的位置问题,先弹出的元素在运算符的右边

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

对于减法,先弹出的是减数,需要带上符号

同理,对于除法,先弹出的是除数,需要变为倒数

2. 作业题目

2.1 最小栈

2.1.1 描述

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

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

示例:

输入:
[“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”]
[[],[-2],[0],[-3],[],[],[],[]]
 
输出:
[null,null,null,null,-3,null,0,-2]
 
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.

提示:

pop、top 和 getMin 操作总是在 非空栈 上调用。

2.1.2 代码

没什么好说的,直观地实现就好

class MinStack:

    def __init__(self):
        self.stack = []

    def push(self, val: int) -> None:
        self.stack.append(val)

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

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

    def getMin(self) -> int:
        if self.stack :
            return min(self.stack)

# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(val)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()
2.1.3 总结

整个类的实例维护的是一个列表,所以要在初始化的时候用self设置变量

top是访问栈顶元素,没有改变栈;pop是弹出栈顶元素,改变了栈

2.2 比较含退格的字符串

2.2.1 描述

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,请你判断二者是否相等。# 代表退格字符。

如果相等,返回 true ;否则,返回 false 。

注意:如果对空文本输入退格字符,文本继续为空。

示例 1:

输入:s = “ab#c”, t = “ad#c”
输出:true
解释:S 和 T 都会变成 “ac”。

示例 2:

输入:s = “ab##”, t = “c#d#”
输出:true
解释:s 和 t 都会变成 “”。

示例 3:

输入:s = “a##c”, t = “#a#c”
输出:true
解释:s 和 t 都会变成 “c”。

示例 4:

输入:s = “a#c”, t = “b”
输出:false
解释:s 会变成 “c”,但 t 仍然是 “b”。

提示:

1 <= s.length, t.length <= 200
s 和 t 只含有小写字母以及字符 ‘#’

进阶:

你可以用 O(N) 的时间复杂度和 O(1) 的空间复杂度解决该问题吗?

2.2.2 代码

直观的做法就是新建两个栈,遇到非#就入栈,遇到#就弹出栈顶,然后再比较

class Solution:
    def delBlank(self, s:str) -> str :
        stack = []
        for i in s:
            if i != '#' :
                stack.append(i)
            elif stack :
                stack.pop()
                
        return "".join(stack)
    
    def backspaceCompare(self, s: str, t: str) -> bool:
    
        return self.delBlank(s) == self.delBlank(t)

进阶的做法就是在原数组上进行比较,而且是逆序

因为退格的#是在字符之后输入的,逆序更好处理

如果是顺序的话,一旦遇到退格符号,还要倒回去处理

所以就是逆序扫描,一一对比两个数组的对应元素

如果遇到#序列,就左移对应的长度来删除元素

一旦遇到不匹配的元素就跳出,两个串肯定不一致

或者就是一个走完了,一个还没完,长度不一致

class Solution:
    def backspaceCompare(self, S: str, T: str) -> bool:
        i, j = len(S) - 1, len(T) - 1
        skipS = skipT = 0

        while i >= 0 or j >= 0:
            while i >= 0:
            # 处理S当中连续的#序列
                if S[i] == "#":
                    skipS += 1
                    i -= 1
                # 如果是#则记下步长并左移
                elif skipS > 0:
                    skipS -= 1
                    i -= 1
                # 一直到字符不为#为止
                # 下标左移记下的步长
                # 即删除对应个字符
                else:
                    break
                # 左移完毕,处理完该#序列
            while j >= 0:
            # 处理T当中连续的#序列
                if T[j] == "#":
                    skipT += 1
                    j -= 1
                # 如果是#则记下步长并左移
                elif skipT > 0:
                    skipT -= 1
                    j -= 1
                # 一直到字符不为#为止
                # 下标左移记下的步长
                # 即删除对应个字符
                else:
                    break
                # 左移完毕,处理完该#序列
            if i >= 0 and j >= 0:
            # 如果这个位置不是尽头
                if S[i] != T[j]:
                    return False
                # 判断当前字符是否一致
            elif i >= 0 or j >= 0:
            # 否则如果其中一个完毕
            # 并且这时另一个未完
                return False
                
            i -= 1
            j -= 1
            # 当前字符一致
            # 左移判断下一位
        
        return True
2.2.3 总结

直观的做法,一般是类似打表的操作,需要有个东西辅助

进阶的做法,那就是在原数组上直接操作,利用指针等

有一个或者多个这种的情况,一般是序列,使用while循环进行处理

2.3 基本计算器 II

2.3.1 描述

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

整数除法仅保留整数部分。

示例 1:

输入:s = “3+2*2”
输出:7

示例 2:

输入:s = " 3/2 "
输出:1

示例 3:

输入:s = " 3+5 / 2 "
输出:5

提示:

1 <= s.length <= 3 * 105
s 由整数和算符 (’+’, ‘-’, ‘*’, ‘/’) 组成,中间由一些空格隔开
s 表示一个 有效表达式
表达式中的所有整数都是非负整数,且在范围 [0, 231 - 1] 内
题目数据保证答案是一个 32-bit 整数

2.3.2 代码

这有点像是之前的逆波兰表达式,但是这个要考虑运算顺序

考虑顺序的话是乘除优先,算完乘除才会有加减操作

那我们直接在栈外运算乘除,对于加减则将符号赋予数字

放在最后一并计算,这样就使得乘除运算优先于加减运算

接下来我们要考虑具体的运算细节,因为输入的是一个字符串

那么对于字符串的引号,我们可以直接忽略掉,不用管

如果要运算,那应该是保证栈中有数字,然后读取运算符号

然后再完整读取完另一个数字之后,运算这个表达式

所以进行运算的触发条件应该是,第二个数字读取完之后

那么如何判断呢?看字符串的当前位,是运算符号还是数字就可以了

如果当前位还是数字,说明这个数值不止一位,还未读取完

如果当前位是一个运算符号,则证明数值读取完整,可以进行运算了

也就是说,我们需要在读取到第二个运算符时,用前一个运算符进行计算

所以我们需要一个变量来存储第一个运算符,并在运算结束更新为第二个运算符

同时读取到的数值也需要一个变量存储,同样在运算完重置便于读取下一个数值

取出栈顶的数值,第一个运算符号presign,以及刚刚读取的数字num做运算就好

运算完毕更新presign为当前位的运算符,同时重置num以便读取下一个数值

class Solution:
    def calculate(self, s: str) -> int:
        n = len(s)
        stack = []
        preSign = '+'
        # 设置前置符号为+,即无影响
        num = 0
        for i in range(n):
            if s[i] != ' ' and s[i].isdigit():
                num = num * 10 + ord(s[i]) - ord('0')
                # 数字可能不止一位,完整读取
            if i == n - 1 or s[i] in '+-*/':
            # 遇到符号之后,开始计算之前的表达式
                if preSign == '+':
                    stack.append(num)
                # 加号直接压入原数字
                elif preSign == '-':
                    stack.append(-num)
                # 减号则压入相反数
                elif preSign == '*':
                    stack.append(stack.pop() * num)
                # 乘法取栈顶运算后再压入
                else:
                    stack.append(int(stack.pop() / num))
                # 除法取栈顶倒数运算后再压入
                preSign = s[i]
                # 记录这次的运算符号,以便下次运算
                num = 0
                # 重置数字,以便读取下一个数字
        return sum(stack)
        # 最后对站内所有元素求和,即计算加减
2.3.3 总结

ord()函数是求传入的字符参数对应的ASCII字符串

一个可运算表达式的基本构造一般是数字+运算符+数字的格式

所以进行运算的触发条件应该是,第二个数字读取完之后

同时我们要注意这是一个字符串,数值可能不止一位

而且最后一个可运算表达式后面没有运算符号了

所以说最后一个运算的触发条件需要特别注明

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值