前言:之前对栈与队列一直停留在表层概念的理解上,不知道怎么用,借刷题的机会来深入理解一下。
文章目录
20 有效括号
20. 有效括号
分析:
- 括号匹配后进先出,很符合栈的特点。
- Python 中 list就可以实现
.pop
的方法,后进先出。 - False 的几种情况:
- ①字符缺失
- ②字符不匹配
- ③遍历字符串后stack中还有值
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
if __name__ == '__main__':
sol=Solution()
s = '(({[]()}])'
a = sol.isValid(s)
print(a)
# 用字典来实现,差别不大
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
1047. 删除字符串中的所有相邻重复项
栈版本
分析:消消乐,相邻相消,还是后进先出,用栈来解决。
- 老方法,还是用
list
作为栈。 - 匹配到相同的就
.pop
不同就.append(item)
。 - 最后输出字符串,把
list
转化为字符串用''.join(res)
来实现
class Solution:
def removeDuplicates(self, s: str) -> str:
res=[]
for item in s:
if res and res[-1]==item:
res.pop()
else:
res.append(item)
return ''.join(res)
双指针版本
分析:
- 双指针的一种思想是:快指针探索,慢指针操作。
- 把探索的值赋给待操作的值
- 当和前一位值相同时,消掉,慢指针退回一格
- 当和前一位值不相同时,慢指针前进一格
- 直到探索完str的全部值为止
class Solution:
def removeDuplicates(self, s: str) -> str:
res=list(s)
s=0
length=len(res)
for f in range(length):
res[s]=res[f]
if s>0 and res[s]==res[s-1]:
s-=1
else:
s+=1
return ''.join(res[0:s])
150. 逆波兰表达式求值
背景知识:
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
- 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
分析:这道题没什么好犹豫的,栈字都已经明晃晃地写出来了,重点是理解一下后缀表达式RPN(Reverse Polish notation)。规则也比较清晰:
- 遇到数字就压入栈;
- 遇到符号就弹出最后的两个数字进行计算;
- 然后把计算结果再压入栈.
- 这里把字符形式的符号变成标准符号用了
eval
函数。 - 有一个小坑:计算的时候两个数字不要弄反,先
pop
出来的数字放在表达式最后一位。
from builtins import str
from typing import List
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
res = []
symbols = list('+-*/')
for item in tokens:
if item in symbols:
f, s = res.pop(), res.pop()
res.append(int(eval(f'{s}{item}{f}')))# 小坑在这里,注意细节
else:
res.append(item)
return int(res.pop())
if __name__ == '__main__':
sol = Solution()
str = ["4", "13", "5", "/", "+"]
a = sol.evalRPN(str)
print(a)
239. 滑动窗口最大值
239. 滑动窗口最大值
这个题的难度等级是hard,感觉可以不做了。
暴力解法
这个题乍看不难,但暴力解法不出意料的超时了…
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
res = []
temp = nums[0:k]
res.append(max(temp))
if k >= len(nums):
return res
else:
for i in nums[k:]:
temp.pop(0)
temp.append(i)
print(temp)
res.append(max(temp))
return res
最后执行的输入长这样:
用队列求解
分析:这里队列并不维护k个元素,而仅保留可能成为最大值的元素。
最后这种方法勉勉强强地通过了。
这里用到的队列有三个函数:
pop
弹出,当滑动窗口移除的值位于队首时执行此操作push
保证push后最大值在队列最前端,如果队列最后的值比push进的值小,则删除队列最末一位的值,循环操作,直到队列前方无值大于push进的值为止。这个操作是保证队列降序的关键。front
最大的数值位于队列最前端,返回便是了。
class MyQueue:
def __init__(self):
self.queue = []
def pop(self, value):
if self.queue and value == self.queue[0]:
self.queue.pop(0)
def push(self, value):
while self.queue and value > self.queue[-1]:
self.queue.pop()
self.queue.append(value)
def front(self):
return self.queue[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
que = MyQueue()
res = []
for i in range(k):
que.push(nums[i])
res.append(que.front())
for i in range(k, len(nums)):
que.pop(nums[i - k])
que.push(nums[i])
res.append(que.front())
return res
更好的版本
- TODO:官方题解的方法似乎都挺快的,回头仔细看看
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
q = collections.deque()
for i in range(k):
while q and nums[i] >= nums[q[-1]]:
q.pop()
q.append(i)
ans = [nums[q[0]]]
for i in range(k, n):
while q and nums[i] >= nums[q[-1]]:
q.pop()
q.append(i)
while q[0] <= i - k:
q.popleft()
ans.append(nums[q[0]])
return ans
347. 前 K 个高频元素
偷懒的内置函数版本
统计list中元素出现的频数有三种:
- 利用字典统计
- 利用Python的
collection
包下的Counter
类统计 - 利用Python的
pandas
包下的value_counts
类统计
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
return [i[0] for i in Counter(nums).most_common(k)]
优先级队列版本
优先级队列是个披着队列外衣的堆。
本题用小顶堆 ,可以用heapq
来实现堆结构。
补充知识:
python字典中get()函数的用法总结
dict[i]=dict.get(i,0)+1
Python heapq库的用法介绍
#时间复杂度: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
总结
- 等再做一遍,再写总结。