LeetCode Cookbook 栈和队列 上篇
栈和队列的习题,上篇主要给出三个类型的题目:(1)model-I 主要是括号匹配、检验、删减类型的题目;(2)model-II则是一些有关栈、队列的数据结构型的题目;(3)model-III 是一些等式计算型题目。这三种类型的题目都非常的经典,且难度适宜,可以多刷几遍巩固对栈和队列结构的认识和应用。
20. 有效的括号(model-I 括号 )
题目链接:20. 有效的括号
题目大意:给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
例如:
输入:s = "()"
输出:true
输入:s = "()[]{}"
输出:true
输入:s = "(]"
输出:false
解题思路:哈希表储存符合要求的 括号对。
class Solution:
def isValid(self, s: str) -> bool:
dic = {'(':')','[':']','{':'}','.':'.'}
stack = ['.']
for ch in s:
if ch in dic: stack.append(ch)
elif dic[stack.pop()] != ch: return False
return len(stack) == 1
856. 括号的分数(model-I 扩展1 加动态规划)
题目链接:856. 括号的分数
题目大意:给定一个平衡括号字符串 S,按下述规则计算该字符串的分数:
- () 得 1 分。
- AB 得 A + B 分,其中 A 和 B 是平衡括号字符串。
- (A) 得 2 * A 分,其中 A 是平衡括号字符串。
例如:
输入: "()"
输出: 1
输入: "(())"
输出: 2
解题思路: 非常有趣的一道题,栈结构解析括号的内外层,动态规划则用以计算得分。
class Solution:
def scoreOfParentheses(self, s: str) -> int:
stack = list([0])
for ch in s:
if ch == '(':
stack.append(0)
else:
num = stack.pop()
# 这个动态规划 属实不要不要的
stack[-1] += max(2*num,1)
return stack.pop()
921. 使括号有效的最少添加(model-I 扩展2)
题目链接: 921. 使括号有效的最少添加表
题目大意:只有满足下面几点之一,括号字符串才是有效的:
- 它是一个空字符串,或者
- 它可以被写成 AB (A 与 B 连接), 其中 A 和 B 都是有效字符串,或者
- 它可以被写作 (A),其中 A 是有效字符串。
给定一个括号字符串 s ,移动N次,你就可以在字符串的任何位置插入一个括号。 - 例如,如果 s = “()))” ,你可以插入一个开始括号为 “(()))” 或结束括号为 “())))” 。
返回 为使结果字符串 s 有效而必须添加的最少括号数。
例如:
输入:s = "())"
输出:1
输入:s = "((("
输出:3
解题思路:利用栈的特性 吐出 符合规则的括号。
class Solution:
def minAddToMakeValid(self, s: str) -> int:
stack = list()
for ch in s:
if ch == '(': stack.append(ch)
elif ch == ')' and stack and stack[-1]=='(':
stack.pop()
else: stack.append(ch)
return len(stack)
1021. 删除最外层的括号(model-I 扩展3)
题目链接:1021. 删除最外层的括号表
题目大意:有效括号字符串为空 “”、“(” + A + “)” 或 A + B ,其中 A 和 B 都是有效的括号字符串,+ 代表字符串的连接。
- 例如,“”,“()”,“(())()” 和 “(()(()))” 都是有效的括号字符串。
如果有效字符串 s 非空,且不存在将其拆分为 s = A + B 的方法,我们称其为原语(primitive),其中 A 和 B 都是非空有效括号字符串。 - 给出一个非空有效字符串 s,考虑将其进行原语化分解,使得:s = P_1 + P_2 + … + P_k,其中 P_i 是有效括号字符串原语。
- 对 s 进行原语化分解,删除分解中每个原语字符串的最外层括号,返回 s 。
例如:
输入:s = "(()())(())"
输出:"()()()"
解释:
输入字符串为 "(()())(())",原语化分解得到 "(()())" + "(())",
删除每个部分中的最外层括号后得到 "()()" + "()" = "()()()"。
解题思路:与 921. 使括号有效的最少添加表 有异曲同工之妙,不过这里 stack 的用途则是控制 ans 中的括号数量。
class Solution:
def removeOuterParentheses(self, s: str) -> str:
ans,stack = "",list()
for ch in s:
if ch == ')':
stack.pop()
if stack:
ans += ch
if ch == '(':
stack.append(ch)
return ans
71. 简化路径(model-I 扩展4 加字符串)
题目链接:71. 简化路径
题目大意:给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 ‘/’ 开头),请你将其转化为更加简洁的规范路径。在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (…) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,‘//’)都被视为单个斜杠 ‘/’ 。 对于此问题,任何其他格式的点(例如,‘…’)均被视为文件/目录名称。
请注意,返回的 规范路径 必须遵循下述格式:
- 始终以斜杠 ‘/’ 开头。
- 两个目录名之间必须只有一个斜杠 ‘/’ 。
- 最后一个目录名(如果存在)不能 以 ‘/’ 结尾。
- 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 ‘.’ 或 ‘…’)。
返回简化后得到的 规范路径 。
例如:
输入:path = "/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。
输入:path = "/a/./b/../../c/"
输出:"/c"
解题思路: 不难 有点像字符串的题目 但结合栈特性 更好、更快的处理。
class Solution:
def simplifyPath(self, path: str) -> str:
file = path.split('/')
stack = []
for ch in file:
if ch == '..':
if stack:
stack.pop()
elif ch and ch != '.':
stack.append(ch)
return '/'+"/".join(stack)
155. 最小栈(model-II 最小栈)
题目链接:155. 最小栈
题目大意:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
-
MinStack() 初始化堆栈对象。
-
void push(int val) 将元素val推入堆栈。
-
void pop() 删除堆栈顶部的元素。
-
int top() 获取堆栈顶部的元素。
-
int 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.
解题思路: 双链表解决最小栈问题。
class MinStack:
# build two stacks realize this goal
def __init__(self):
self.stack = list()
self.minStack = [math.inf]
def push(self, val: int) -> None:
# The stack and minStack have same elements
self.stack.append(val)
self.minStack.append(min(val,self.minStack[-1]))
def pop(self) -> None:
self.stack.pop()
self.minStack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.minStack[-1]
# 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()
225. 用队列实现栈(model-II 队列->栈)
题目链接:225. 用队列实现栈
题目大意:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
- void push(int x) 将元素 x 压入栈顶。
- int pop() 移除并返回栈顶元素。
- int top() 返回栈顶元素。
- boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
例如:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
解题思路:双队列 实现 栈入栈出。
class MyStack:
def __init__(self):
# 主要是通过两个队列将数组的顺序进行翻转
self.q1 = collections.deque()
self.q2 = collections.deque()
def push(self, x: int) -> None:
self.q2.append(x)
while self.q1:
self.q2.append(self.q1.popleft())
self.q1,self.q2 = self.q2,self.q1
def pop(self) -> int:
return self.q1.popleft()
def top(self) -> int:
return self.q1[0]
def empty(self) -> bool:
return not self.q1
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
232. 用栈实现队列(model-II 栈->队列)
题目链接:232. 用栈实现队列
题目大意:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty),实现 MyQueue 类:
- void push(int x) 将元素 x 推到队列的末尾
- int pop() 从队列的开头移除并返回元素
- int peek() 返回队列开头的元素
- boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
例如:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
解题思路:双栈 加上 一个旗帜 更快一些。
class MyQueue:
def __init__(self):
# The task of stack1 is collecting numbers
# And the mission of stack2 is simulating queue。
self.stack1 = list()
self.stack2 = list()
# 设置一个旗杆专门用来处理 只有一个元素的入队与出队
self.flag = None
def push(self, x: int) -> None:
if not self.stack1: self.flag = x
self.stack1.append(x)
def pop(self) -> int:
if not self.stack2:
while self.stack1:
self.stack2.append(self.stack1.pop())
# remember cancel self.flag
self.flag = None
return self.stack2.pop()
def peek(self) -> int:
if self.stack2:
return self.stack2[-1]
return self.flag
def empty(self) -> bool:
return len(self.stack1)==0 and len(self.stack2)==0
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
895. 最大频率栈(model-II 双栈->频率栈)
题目链接:895. 最大频率栈
题目大意:设计一个类似堆栈的数据结构,将元素推入堆栈,并从堆栈中弹出出现频率最高的元素。
实现 FreqStack 类:
- FreqStack() 构造一个空的堆栈。
- void push(int val) 将一个整数 val 压入栈顶。
- int pop() 删除并返回堆栈中出现频率最高的元素。如果出现频率最高的元素不只一个,则移除并返回最接近栈顶的元素。
例如:
输入:
["FreqStack","push","push","push","push","push","push","pop","pop","pop","pop"],
[[],[5],[7],[5],[7],[4],[5],[],[],[],[]]
输出:[null,null,null,null,null,null,null,5,7,5,4]
解释:
FreqStack = new FreqStack();
freqStack.push (5);//堆栈为 [5]
freqStack.push (7);//堆栈是 [5,7]
freqStack.push (5);//堆栈是 [5,7,5]
freqStack.push (7);//堆栈是 [5,7,5,7]
freqStack.push (4);//堆栈是 [5,7,5,7,4]
freqStack.push (5);//堆栈是 [5,7,5,7,4,5]
freqStack.pop ();//返回 5 ,因为 5 出现频率最高。堆栈变成 [5,7,5,7,4]。
freqStack.pop ();//返回 7 ,因为 5 和 7 出现频率最高,但7最接近顶部。堆栈变成 [5,7,5,4]。
freqStack.pop ();//返回 5 ,因为 5 出现频率最高。堆栈变成 [5,7,4]。
freqStack.pop ();//返回 4 ,因为 4, 5 和 7 出现频率最高,但 4 是最接近顶部的。堆栈变成 [5,7]。
解题思路: 设置三个变量 分别用来: (1)计频;(2)记元素;(3)固定最大频次。
class FreqStack:
def __init__(self):
self.counter = collections.Counter()
self.hash_map = collections. defaultdict(list)
self.max_freq = 0
def push(self, x: int) -> None:
f = self.counter[x]+1
self.counter[x] = f
if f > self.max_freq:
self.max_freq = f
self.hash_map[f].append(x)
def pop(self) -> int:
x = self.hash_map[self.max_freq].pop()
self.counter[x] -= 1
if not self.hash_map[self.max_freq]:
self.max_freq -= 1
return x
# Your FreqStack object will be instantiated and called as such:
# obj = FreqStack()
# obj.push(val)
# param_2 = obj.pop()
150. 逆波兰表达式求值(model-III 算式题)
题目链接:150. 逆波兰表达式求值
题目大意:根据 逆波兰表示法,求表达式的值。有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。注意 两个整数之间的除法只保留整数部分。可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
例如:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
输入: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
解题思路 :注意:python3 的地板除 “//” 是整数除法, “-3 // 2 = -2” ;python3 的除法 “/” 是浮点除法, “-3 / 2 = -1.5” ; 应该向0取整 即 int(-1.5) = -1。 这道题的 try except finally 结构也很有特色和实用性。
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
option = {
"+":add,"-":sub,"*":mul,"/":lambda x,y:int(x/y),
}
stack =list()
for token in tokens:
try:
num = int(token)
except ValueError:
num2 = stack.pop()
num1 = stack.pop()
num = option[token](num1,num2)
finally:
stack.append(num)
return stack[0]
227. 基本计算器 II(model-III 延展1)
题目链接:227. 基本计算器 II
题目大意:给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。整数除法仅保留整数部分。你可以假设给定的表达式总是有效的。所有中间结果将在
[
−
2
31
,
2
31
−
1
]
[-2^{31}, 2^{31} - 1]
[−231,231−1] 的范围内。注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。
例如:
输入:s = "3+2*2"
输出:7
输入:s = " 3+5 / 2 "
输出:5
解题思路:很不错的一道题 相对于 150. 逆波兰表达式求值 中的字符串中数字和预算符号顺序,这道题看得更舒服一些。
class Solution:
def calculate(self, s: str) -> int:
n = len(s)
# 一个装数字 一个灵活变动存储符号
stack,op = list(),'+'
num = 0
for i,ch in enumerate(s):
if ch.isdigit():
num = 10*num + int(ch)
if i == n-1 or ch in '+-*/':
if op == '+':
stack.append(num)
elif op == '-':
stack.append(-num)
elif op == '*':
stack.append(stack.pop()*num)
elif op == '/':
stack.append(int(stack.pop()/num))
op,num = ch,0
return sum(stack)
224. 基本计算器(model-III 延展2)
题目链接:224. 基本计算器
题目大意:给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。
例如:
输入:s = "1 + 1"
输出:2
输入:s = "(1+(4+5+2)-3)+(6+8)"
输出:23
解题思路: 如果仅有加减操作时,主要的便是 优先展开括号内内容进行计算 相对于 227. 基本计算器 II 此题的字符串更简单一些,这道题可以扩展一下,将加减扩展至 乘除求余等复杂操作,不过这就需要进行各种运算优先级的设定了。
class Solution:
def calculate(self, s: str) -> int:
stack,sign = list([1]),1
# print(stack)
ans,num,n = 0,0,len(s)
for i,ch in enumerate(s):
if ch.isdigit():
num = 10*num + int(ch)
if i == n-1 or ch in '+-()':
ans += sign * num
if ch == '+':
sign = stack[-1]
elif ch == '-':
sign = -stack[-1]
elif ch == '(':
stack.append(sign)
elif ch == ')':
stack.pop()
num = 0
return ans
总结
这次 12 道题,继续加油,再把速度往上提一提,努力,奋斗。