Day10,Day12:栈+队列速通
摆烂了。。。完全摆烂了,反思一下自己,总是想要把每一次都做得很好,导致压力先把自己搞摆烂了,现在开始转变一下心态重新开始,能跟一点是一点吧,完成比完美更重要
232.用栈实现队列
225. 用队列实现栈
基础操作,只是比较繁琐,就不贴上来了
20.有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
这个题目要把握好如何使用栈,并且搞清楚不匹配的情况有哪些。
还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
class Solution(object):
def isValid(self, s):
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
1047. 删除字符串中的所有相邻重复项
栈的经典应用,跟前一个题目一样,都是消消乐型的题目。
而且只需要控制好什么时候入栈,然后需要放入元素时比较一下栈顶就ok
class Solution(object):
def removeDuplicates(self, s):
stack = []
for item in s:
if not stack:
stack.append(item)
continue
if item == stack[-1]:
stack.pop()
else:
stack.append(item)
return "".join(stack)
150. 逆波兰表达式求值
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
实际上就是一个算数的栈表达,很简单,但编写代码要考虑的就比较多,我使用了python的eval()函数进行了字符串的算式运算,在本地跑的结果是正确的,但是放在力扣上跑就错误了,不知道为啥
class Solution(object):
def evalRPN(self, tokens):
stack = []
for token in tokens:
if token not in ["+","-","*","/"]:
stack.append(token)
else:
op1 = stack.pop()
op2 = stack.pop()
r = int(eval(op2 + token + op1))
stack.append(str(r))
return int(stack[-1])
还是借鉴一下答案给的方法吧
op_map = {'+': add, '-': sub, '*': mul, '/': div}
stack.append(self.op_map[token](op1, op2))
还要注意的是 第一个op1出来的在运算符后面
239. 滑动窗口最大值
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
这里需要实现的一个数据结构是,希望可以像队列一样pop和push,同时可以调用一个方法来返回队内的最大值
如果使用暴力方法去查找队内最大值,那有点浪费
于是我们可以选择去维护一个队列,在这个队列中最大值永远在队首,队伍后面跟着的都是比队首元素小的(可能在某个时候成为队伍最大值的元素)
那么这样的一个队列,在push一个元素时,要把所有比这个元素小的元素都先从队尾排出;在pop一个元素时,只需要看这个元素是否是队头元素,如果相同就pop队头即可;这样之后,要找到这个队中最大的数字永远在队头,只要pop队头就好。
class MyQueue:
def __init__(self):
self.queue = deque() #这里需要使用deque实现单调队列,直接使用list会超时
def pop(self, value):
if self.queue and value == self.queue[0]:
self.queue.popleft()
def push(self, value):
while self.queue and self.queue[-1] < value:
self.queue.pop()
self.queue.append(value)
def front(self):
return self.queue[0]
class Solution(object):
def maxSlidingWindow(self, nums, k):
que = MyQueue()
result = []
j = -k
for i in range(len(nums)):
if j < 0 :
j += 1
que.push(nums[i])
continue
else:
result.append(que.front())
que.push(nums[i])
que.pop(nums[j])
j += 1
result.append(que.front())
return result
347.前k个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案
前k个高频元素,最好的方法肯定是使用堆来进行动态筛选,实际上也可以称为优先级队列。每个元素进行插入堆的操作,每一次插入一个大小为k的堆用时为O(logk),所以时间复杂度为O(nlogk)。当堆的大小大于k时就挤出一个最小的值(堆顶的值),所以这个题目使用小根堆来解决。
第一次使用python来写堆操作,还是熟悉一下heapq模块
Python中heapq模块浅析_heapq.heappush-CSDN博客
现在有一个列表heap
heapq.heappush(heap,a)是往堆中添加新值,此时自动建立了小根堆
但heapq里面没有直接提供建立大根堆的方法,可以采取如下方法:每次push时给元素加一个负号(即取相反数),此时最小值变最大值,反之亦然,那么实际上的最大值就可以处于堆顶了,返回时再取负即可。
heapq.heapfy(heap)是以线性时间将一个列表转化为小根堆
heapq.heappop()是
从堆中弹出并返回最小的值
import heapq
class Solution(object):
def topKFrequent(self, nums, k):
map_ = {}
for i in range(len(nums)):
map_[nums[i]] = map_.get(nums[i], 0) + 1
#这个很实用,可以用来快速计算频次
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)
result = [0] * k
for i in range(k):
result[i] = heapq.heappop(pri_que)[1]
return result
栈和队列的总结
赶进度一天就结束了,来用自己的话总结一下
(1)栈与队列的理论基础
一道面试题:栈里面的元素在内存中是连续分布的么?
这个问题有两个陷阱:
- 陷阱1:栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中不一定是连续分布的。
- 陷阱2:缺省情况下,默认底层容器是deque,那么deque在内存中的数据分布是什么样的呢? 答案是:不连续的,下文也会提到deque。
在python中,deque(双向队列)是collections模块常用类型,是栈和队列的一种广义实现,deque是"double-end queue"的简称;deque支持线程安全、有效内存地以近似O(1)的性能在deque的两端插入和删除元素,尽管list也支持相似的操作,但是它主要在固定长度操作上的优化,从而在pop(0)和insert(0,v)(会改变数据的位置和大小)上有O(n)的时间复杂度。
(2)栈的典型应用
括号匹配、字符串去重,逆波兰表达式,总结来说,都是“消消乐”问题
(3)队列的经典应用
滑动窗口最大值(单调队列):维护一个队列使得其中的元素输出总是单调的(?)可以说的不太严谨,感觉是一个自定义调整维护的队列,使其完成要求
求频率topk(堆):这里要注意,找最大topk是维护小根堆,因为需要不断地淘汰最小的那个元素。
感觉这两个题目实际上也不是完全的队列,“队列”的底层是deque双向队列,是可以两边拓展的,队列更像是一种思想,在实现过程中还需要多多思考