Task03:栈
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
字符串
一个可运算表达式的基本构造一般是数字+运算符+数字
的格式
所以进行运算的触发条件应该是,第二个数字读取完之后
同时我们要注意这是一个字符串,数值可能不止一位
而且最后一个可运算表达式后面没有运算符号了
所以说最后一个运算的触发条件需要特别注明