栈与队列系列1
栈与队列理论基础
简单来讲:就是栈和队列都是不可迭代的,同时底层实现可以是list等,包装后实现对外的push和pop接口。
代码随想录的理论链接如下:
栈和队列理论基础
232 用栈实现队列
第一次接触相关代码,不知道怎么写,就直接看答案。
明白了,其中最重要就是pop操作,需要使用一个stack_out的栈,认真理解此段代码。
代码随想录的代码
class MyQueue:
def __init__(self):
"""
in主要负责push,out主要负责pop
"""
self.stack_in = []
self.stack_out = []
def push(self, x: int) -> None:
"""
有新元素进来,就往in里面push
"""
self.stack_in.append(x)
def pop(self) -> int:
"""
Removes the element from in front of queue and returns that element.
"""
if self.empty():
return None
if self.stack_out:
return self.stack_out.pop()
else:
for i in range(len(self.stack_in)):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
def peek(self) -> int:
"""
Get the front element.
"""
ans = self.pop()
self.stack_out.append(ans)
return ans
def empty(self) -> bool:
"""
只要in或者out有元素,说明队列不为空
"""
return not (self.stack_in or self.stack_out)
第二遍自己写的代码
class MyQueue:
def __init__(self):
self.pushin = []
self.popout = []
def push(self, x: int) -> None:
self.pushin.append(x)
def pop(self) -> int:
if self.popout == []:
n = len(self.pushin)
for i in range(n-1,-1,-1):
self.popout.append(self.pushin.pop())
return self.popout.pop()
else :
return self.popout.pop()
def peek(self) -> int:
res = self.pop()
self.popout.append(res)
return res
def empty(self) -> bool:
if self.pushin == [] and self.popout == [] :
return True
else :
return False
225 用队列实现栈
用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行!(这一点一定要注意!!!)
队列模拟栈,其实一个队列就够了,先说一说两个队列来实现栈的思路。
队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。
所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全用来备份的!
不会写,还是学习卡哥的代码
第一遍看不太懂,对Python各种数据结构的特性以及内置函数不了解,而且这种利用队列实现栈的思路也是之前没有接触过的。
代码随想录的代码
1、使用两个队列实现
from collections import deque
class MyStack:
def __init__(self):
"""
Python普通的Queue或SimpleQueue没有类似于peek的功能
也无法用索引访问,在实现top的时候较为困难。
用list可以,但是在使用pop(0)的时候时间复杂度为O(n)
因此这里使用双向队列,我们保证只执行popleft()和append(),因为deque可以用索引访问,可以实现和peek相似的功能
in - 存所有数据
out - 仅在pop的时候会用到
"""
self.queue_in = deque()
self.queue_out = deque()
def push(self, x: int) -> None:
"""
直接append即可
"""
self.queue_in.append(x)
def pop(self) -> int:
"""
1. 首先确认不空
2. 因为队列的特殊性,FIFO,所以我们只有在pop()的时候才会使用queue_out
3. 先把queue_in中的所有元素(除了最后一个),依次出列放进queue_out
4. 交换in和out,此时out里只有一个元素
5. 把out中的pop出来,即是原队列的最后一个
tip:这不能像栈实现队列一样,因为另一个queue也是FIFO,如果执行pop()它不能像
stack一样从另一个pop(),所以干脆in只用来存数据,pop()的时候两个进行交换
"""
if self.empty():
return None
for i in range(len(self.queue_in) - 1):
self.queue_out.append(self.queue_in.popleft())
self.queue_in, self.queue_out = self.queue_out, self.queue_in # 交换in和out,这也是为啥in只用来存
return self.queue_out.popleft()
def top(self) -> int:
"""
1. 首先确认不空
2. 我们仅有in会存放数据,所以返回第一个即可
"""
if self.empty():
return None
return self.queue_in[-1]
def empty(self) -> bool:
"""
因为只有in存了数据,只要判断in是不是有数即可
"""
return len(self.queue_in) == 0
2、优化,使用一个队列实现
class MyStack:
def __init__(self):
self.que = deque()
def push(self, x: int) -> None:
self.que.append(x)
def pop(self) -> int:
if self.empty():
return None
for i in range(len(self.que)-1):
self.que.append(self.que.popleft())
return self.que.popleft()
def top(self) -> int:
if self.empty():
return None
return self.que[-1]
def empty(self) -> bool:
return not self.que
第二遍自己写的代码
1、用两个队列,还是沿用代码随想录说的,由于List调用pop(0)时间复杂度过高,用deque()的popleft()代替。
from collections import deque
# 我写的代码里,没有判断空的操作,默认了所有操作全部合法
class MyStack:
def __init__(self):
self.dq1 = deque()
self.dq2 = deque()
def push(self, x: int) -> None:
self.dq1.append(x)
def pop(self) -> int:
n = len(self.dq1)
for i in range(n-1):
self.dq2.append(self.dq1.popleft())
res = self.dq1.popleft()
self.dq2 , self.dq1 = self.dq1 , self.dq2
return res
def top(self) -> int:
# 这也行啊,直接索引
return self.dq1[-1]
def empty(self) -> bool:
if len(self.dq1) == 0 :
return True
else :
return False
2、用一个队列,没啥好说的,会了两个队列的,就会一个队列的。
from collections import deque
# 我写的代码里,没有判断空的操作,默认了所有操作全部合法
class MyStack:
def __init__(self):
self.dq = deque()
def push(self, x: int) -> None:
self.dq.append(x)
def pop(self) -> int:
n = len(self.dq)
for i in range(n-1):
self.dq.append(self.dq.popleft())
res = self.dq.popleft()
return res
def top(self) -> int:
# 这也行啊,直接索引
return self.dq[-1]
def empty(self) -> bool:
if len(self.dq) == 0 :
return True
else :
return False
20 有效的括号
括号匹配是使用栈解决的经典问题。(这个很好想,我也想到了)
由于栈结构的特殊性,非常适合做对称匹配类的题目。
只想到栈是不够的,要明确有几种不匹配的情况,这是我没有想到的。
1、第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
2、第二种情况,括号没有多余,但是 括号的类型没有匹配上。
3、第三种情况,字符串里右方向的括号多余了,所以不匹配。
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。
分析完之后,代码其实就比较好写了,
但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
自己想当然的先倒序后判断是错误的
反例:“()[]{}”
class Solution:
def isValid(self, s: str) -> bool:
s = list(s)
rs = s.copy()
rs.reverse()
n = len(s)
for i in range(n):
if s[i] == '(' and rs[i] != ')' :
return False
elif s[i] == '[' and rs[i] != ']' :
return False
elif s[i] == '{' and rs[i] != '}' :
return False
elif s[i] == ')' and rs[i] != '(' :
return False
elif s[i] == ']' and rs[i] != '[' :
return False
elif s[i] == '}' and rs[i] != '{' :
return False
else :
pass
return True
代码随想录的代码
确实牛,确实写不出来这些,数据结构是我的弱项。
# 方法一,仅使用栈,更省空间
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for item in s:
if item == '(':
stack.append(')')
elif item == '[':
stack.append(']')
elif item == '{':
stack.append('}')
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
# 方法二,使用字典
class Solution:
def isValid(self, s: str) -> bool:
stack = []
mapping = {
'(': ')',
'[': ']',
'{': '}'
}
for item in s:
if item in mapping.keys():
stack.append(mapping[item])
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
第二遍自己写的代码
class Solution:
def isValid(self, s: str) -> bool:
stack = []
n = len(s)
if n % 2 == 0 :
for i in range(n):
if s[i]=='(' :
stack.append(')')
elif s[i]=='[' :
stack.append(']')
elif s[i]=='{' :
stack.append('}')
# 我这里写的和代码随想录的不一样,他用了索引,但是栈是没有索引的,所以在这里pop了之后
# 下面的 else 直接contiue就可以了,不需要再次pop
# 但是其实用索引也可以,就相当于调用了栈的peak()方法
elif stack == [] or s[i] != stack.pop():
return False
else :
continue
if stack == [] :
return True
else :
return False
else :
return False
1047 删除字符串中的所有相邻重复项
唉,每道题都不会。
思路这里就不记录了,过两天复习的时候要重点过一遍“栈和队列”的所有题。
明确一点:Python中的List的append和pop方法,本质上就是栈的先进后出。
代码随想录的代码
# 方法一,使用栈
class Solution:
def removeDuplicates(self, s: str) -> str:
res = list()
for item in s:
if res and res[-1] == item:
res.pop()
else:
res.append(item)
return "".join(res) # 字符串拼接
这个不让使用栈,而使用双指针方法的,还是牛逼。
# 方法二,使用双指针模拟栈,如果不让用栈可以作为备选方法。
class Solution:
def removeDuplicates(self, s: str) -> str:
res = list(s)
slow = fast = 0
length = len(res)
while fast < length:
# 如果一样直接换,不一样会把后面的填在slow的位置
res[slow] = res[fast]
# 如果发现和前一个一样,就退一格指针
if slow > 0 and res[slow] == res[slow - 1]:
slow -= 1
else:
slow += 1
fast += 1
return ''.join(res[0: slow])
第二遍自己写的代码
class Solution:
def removeDuplicates(self, s: str) -> str:
stack = []
n = len(s)
for i in range(n):
if stack == [] :
stack.append(s[i])
elif stack[-1] == s[i] :
stack.pop()
else :
stack.append(s[i])
return ''.join(stack)
150 逆波兰表达式求值
思路:用栈操作运算,遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
递归是用栈来实现的。
栈与递归之间在某种程度上是可以转换的。
其实逆波兰表达式相当于是二叉树中的后序遍历。 大家可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。
但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后序遍历的方式把二叉树序列化了,就可以了。
在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么这岂不就是一个相邻字符串消除的过程,和1047.删除字符串中的所有相邻重复项 (opens new window)中的对对碰游戏是不是就非常像了。
代码随想录的代码
from operator import add, sub, mul
class Solution:
op_map = {'+': add, '-': sub, '*': mul, '/': lambda x, y: int(x / y)}
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
if token not in {'+', '-', '*', '/'}:
stack.append(int(token))
else:
op2 = stack.pop()
op1 = stack.pop()
stack.append(self.op_map[token](op1, op2)) # 第一个出来的在运算符后面
return stack.pop()
另一种可行,但因为使用eval相对较慢的方法:
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for item in tokens:
if item not in {"+", "-", "*", "/"}:
stack.append(item)
else:
first_num, second_num = stack.pop(), stack.pop()
stack.append(
int(eval(f'{second_num} {item} {first_num}')) # 第一个出来的在运算符后面
)
return int(stack.pop()) # 如果一开始只有一个数,那么会是字符串形式的
第二遍自己写的代码
向零截断,投机取巧的办法,按浮点除法做,然后int()
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
n = len(tokens)
for i in range(n):
if tokens[i] == '+' :
num1 = stack.pop()
num2 = stack.pop()
temp = num1 + num2
stack.append(temp)
elif tokens[i] == '-' :
num1 = stack.pop()
num2 = stack.pop()
temp = num2 - num1
stack.append(temp)
elif tokens[i] == '*' :
num1 = stack.pop()
num2 = stack.pop()
temp = num2 * num1
stack.append(temp)
elif tokens[i] == '/' :
num1 = stack.pop()
num2 = stack.pop()
temp = int(num2 / num1)
stack.append(temp)
else :
stack.append(int(tokens[i]))
return stack.pop()
239 滑动窗口最大值
真的绕,看不懂。
代码随想录的代码
from collections import deque
class MyQueue: #单调队列(从大到小
def __init__(self):
self.queue = deque() #这里需要使用deque实现单调队列,直接使用list会超时
#每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
#同时pop之前判断队列当前是否为空。
def pop(self, value):
if self.queue and value == self.queue[0]:
self.queue.popleft()#list.pop()时间复杂度为O(n),这里需要使用collections.deque()
#如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
#这样就保持了队列里的数值是单调从大到小的了。
def push(self, value):
while self.queue and value > self.queue[-1]:
self.queue.pop()
self.queue.append(value)
#查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
def front(self):
return self.queue[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
que = MyQueue()
result = []
for i in range(k): #先将前k的元素放进队列
que.push(nums[i])
result.append(que.front()) #result 记录前k的元素的最大值
for i in range(k, len(nums)):
que.pop(nums[i - k]) #滑动窗口移除最前面元素
que.push(nums[i]) #滑动窗口前加入最后面的元素
result.append(que.front()) #记录对应的最大值
return result
第二遍自己写的代码
from collections import deque
class DQ:
def __init__(self):
self.dq = deque()
def push(self,x):
if len(self.dq) == 0 :
self.dq.append(x)
else :
while self.dq and x > self.dq[-1] :
self.dq.pop()
self.dq.append(x)
def pop(self,x):
if self.dq and self.dq[0]==x:
self.dq.popleft()
def front(self):
return self.dq[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
dq = DQ()
result = []
for i in range(k):
dq.push(nums[i])
result.append(dq.front())
for i in range(k,len(nums)):
dq.pop(nums[i-k])
dq.push(nums[i])
result.append(dq.front())
return result
347 前K个高频元素
注意点:用小顶堆而不是大顶堆,堆只维护K个元素,pop走小的,剩下的才是大的。
Python中的heapq库介绍(Python中的小顶堆):上链接!
代码随想录的代码
第一次接触Python中的堆,几年前学的C语言中的堆也忘干净了,所以在第二遍的时候也无法自己写出代码,就看看代码随想录的解答吧。
#时间复杂度:O(nlogk)
#空间复杂度:O(n)
import heapq
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
#要统计元素出现频率
map_ = {} #nums[i]:对应出现的次数
for i in range(len(nums)):
map_[nums[i]] = map_.get(nums[i], 0) + 1
#对频率排序
#定义一个小顶堆,大小为k
pri_que = [] #小顶堆
#用固定大小为k的小顶堆,扫描所有频率的数值
for key, freq in map_.items():
heapq.heappush(pri_que, (freq, key))
if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
heapq.heappop(pri_que)
#找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
result = [0] * k
for i in range(k-1, -1, -1):
result[i] = heapq.heappop(pri_que)[1]
return result