文章目录
1. 栈与队列理论基础
- 栈stack:先进后出
- 队列queue:先进先出
2. 用栈实现队列
2.1 题目
232.用栈实现队列
仅使用两个栈实现队列的下列操作:
- push(x) – 将一个元素放入队列的尾部。
- pop() – 从队列首部移除元素。
- peek() – 返回队列首部的元素。
- empty() – 返回队列是否为空。
2.2 分析
- 两个栈:进栈1 & 出栈2
- push:新元素放入 栈1
- pop:将栈1中所有元素放到栈2,即可实现队列所需要的排序
- peek:通过pop操作可得到首部元素
- empty:即判断栈1和2是否均为空
2.3 代码
python 与官方一致
class MyQueue:
def __init__(self):
self.stack_in = []
self.stack_out = []
def push(self, x: int) -> None:
self.stack_in.append(x)
def pop(self) -> int:
if self.empty():
return None
if not self.stack_out:
n = len(self.stack_in)
for i in range(n):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
def peek(self) -> int:
temp = self.pop()
self.stack_out.append(temp)
return temp
def empty(self) -> bool:
return not (self.stack_in or self.stack_out)
# 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()
时间复杂度:
O
(
1
)
O(1)
O(1)
空间复杂度:
O
(
n
)
O(n)
O(n)
3. 用队列实现栈
3.1 题目
225. 用队列实现栈
仅使用两个队列实现栈的下列操作:
- push(x) – 元素 x 入栈
- pop() – 移除栈顶元素
- top() – 获取栈顶元素
- empty() – 返回栈是否为空
3.2 分析
- 这个问题并非“用栈实现队列”问题的对称应用。仔细思考会发现用2个队列跟用1个队列没有本质区别,因为俩队列拼起来就是一个队列哈哈
- 理论上看,没有了多个队列空间上的帮助,就只能牺牲时间了,实现pop操作只能遍历一遍找到尾巴
- 队列pop出来再push进去,执行n-1次,此时再pop就是原来的尾巴出栈了
3.3 代码
python 用list实现(跟官方不同)
class MyStack:
def __init__(self):
self.queue = []
def push(self, x: int) -> None:
return self.queue.append(x)
def pop(self) -> int:
n = len(self.queue)
for i in range(n-1):
temp = self.queue.pop()
self.queue.append(temp)
return self.queue.pop()
def top(self) -> int:
n = len(self.queue)
for i in range(n):
temp = self.queue.pop()
self.queue.append(temp)
return temp
def empty(self) -> bool:
return not self.queue
# 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()
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
4. 有效的括号
4.1 题目
4.2 分析
栈的经典应用——对称匹配类的题目
注意点:
- 一一匹配:遍历后栈不能非空,否则就是有多余的左括号没有被匹配;同理,来个右括号必须在栈中有左括号匹配
- 匹配不能穿插:栈顶等着小括号,你就不能拿中括号来配
4.3 代码
python
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for char in s:
if char == '(':
stack.append(')')
elif char == '[':
stack.append(']')
elif char == '{':
stack.append('}')
elif (not stack) or stack[-1]!=char:
return False
else:
stack.pop()
if stack:
return False
return True
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
5. 删除字符串中的所有相邻重复项
5.1 题目
5.2 代码
python
class Solution:
def removeDuplicates(self, s: str) -> str:
stack = []
for char in s:
if stack and stack[-1]==char:
stack.pop()
else:
stack.append(char)
return ''.join(stack)
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
5.4 相关题目
6. 逆波兰表达式求值
6.1 题目
6.3 代码
python
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for item in tokens:
if item not in {'+', '-', '*', '/'}:
stack.append(int(item))
continue
temp1 = stack.pop()
temp2 = stack.pop()
if item == '+':
stack.append(temp1+temp2)
elif item == '-':
stack.append(temp2-temp1)
elif item == '*':
stack.append(temp1*temp2)
elif item == '/':
stack.append(int(temp2/temp1))
else:
return None
return stack.pop()
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
7. 滑动窗口最大值
7.1 题目
7.2 分析
这道题很有意思,可以拆解为3个部分的问题:
- 单调队列的维护
- 最大元素的移出
- 最大元素的记录
7.2.1 单调队列的维护
- 本题的核心破题点就是创造一个单调不增的队列,这也是从暴力算法升级为线性复杂度算法的关键:我们舍弃掉小元素,只维护大元素
- 具体实现方式:当来一个新元素时,舍弃掉队内所有比它小的值,然后入队,由此保证队内单调不增,保存的都是“有价值”的“最大值候选人”
7.2.2 最大元素的移出
- 窗口在滑动,每次都会丢掉一个元素
- 当需要丢掉的元素在队中时我们就需要将其出队
- (队列不仅有单调性还有时序性,与丢弃顺序一致,所以只用看是不是轮到队首元素移出了)
- 当指针i>=k后每次移出窗口的元素为nums[i-k]
注意:由1、2的需求就可以发现,我们需要的数据结构是一个可以两头弹出的“队列”,这也是本题的关键(弹出头是为了移出离开窗口的元素;弹出尾是为了维护队列的单调性)
7.2.3 最大元素的记录
- 那么费力搭好戏台了,那么我们到底要看什么?
- 其实窗口的最大值就是上面单调队列的头头
- 不难知道,从指针i=k-1开始取
7.3 代码
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
queue = deque()
result = []
n = len(nums)
for i in range(n):
# 维护单调递减队列
while queue and queue[-1]<nums[i]:
queue.pop()
queue.append(nums[i])
# 弹出离开窗口的最大值
if i >= k and nums[i-k]==queue[0]:
queue.popleft()
# 记录窗口最大值
if i >=(k-1):
result.append(queue[0])
return result
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
k
)
O(k)
O(k)
8. 前 K 个高频元素
8.1 题目
8.2 分析
- 用字典统计数字出现的频率
- 频率排序,取前k个
8.3 代码
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
# 统计频率
time_dict = defaultdict(int)
for num in nums:
time_dict[num] += 1
# 更改字典:键key-频次;值value-数字(list)
index_dict = defaultdict(list)
for num in time_dict:
index_dict[time_dict[num]].append(num)
# 排序
times = list(index_dict.keys())
times.sort()
result = []
count = 0
while times and count<k:
result += index_dict[times[-1]]
count += len(index_dict[times[-1]])
times.pop()
return result[0:k]
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
空间复杂度:
O
(
n
)
O(n)
O(n)