目录
150. 逆波兰表达式求值
题目链接:150. 逆波兰表达式求值 - 力扣(LeetCode)
文章讲解:代码随想录
解题卡点:无
理解了逆波兰表达式 (后缀表达式) 怎么计算的就好。遇到数字则入栈,遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中,依次迭代。
Tips:
①后缀表达式对计算机来说非常友好,无需判断中缀表达式的括号、运算符优先级等。
②str.isdigit()只能判断正数,由于负数前有个'-',.isdigit()会将负数判为False。因此只要去掉负号再判断就行,即str.lstrip('-').isdigit()。
③python3的'//'是地板除法,1.5→1,-1.5→-2;'/'是浮点数除法,-1.5→-1.5。想要计算结果向0截断,可使用int(a/b),1.5→1,-1.5→-1。
④使用eval()函数可将字符形式的运算符'+'转为能计算的运算符。另外,eval()计算速度相对较慢,也可以定义一个字符运算符和真正运算符的字典进行映射。
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for i in tokens:
if i.lstrip('-').isdigit(): # 元素为数字,入栈
stack.append(i)
else: # 元素为运算符,将两个元素出栈进行计算,并将计算结果入栈
b = stack.pop()
a = stack.pop()
if i == '/': # 题目要求除法向0截断,故额外定义一下
val = str(int(eval(a+i+b)))
else:
val = str(eval(a+i+b))
stack.append(val)
res = int(stack.pop())
return res
# 时间复杂度 O(n)
# 空间复杂度 O(n)
239. 滑动窗口最大值
题目链接:239. 滑动窗口最大值 - 力扣(LeetCode)
文章讲解:代码随想录
解题卡点:只想到暴力解,没想到优化解法
本题需要左弹出和右弹出,故使用双端队列deque作为基础数据结构。同时队列中元素保持了从大到小的单调性,因此也为单调队列。
队列中没有必要维护滑动窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素,同时保证队列里的元素数值是由大到小的。
队列的弹出:如果滑动窗口移除的元素等于队尾元素,那么弹出队尾元素,否则不用任何操作。每次pop元素,移除的都是队列最左侧的值 (确认最大值还在不在窗口里)。
队列的加入:如果加入的元素大于队头元素的数值,那么就将队头的元素弹出,直到加入的元素小于队头的元素为止。每次push元素,从右侧开始循环移除比自己小的值 (保证队列是递减的,而且每一个新的值都可以加入队列)。
本题核心就是,我们维护的是一个动态的实力排行榜 (这里体现的就是单调,实力越强,排名越靠前),那么第一名自然是最强的 (队头是最大的元素)。就像一个擂台,当有新人进来,他会把不如自己的人pk下去,更新排行榜。当老大退役 (pop),第二名就变成了第一名。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
from collections import deque
res = []
mque = deque() # 单调队列,队列中元素队尾最大,队头最小
for i in range(len(nums)):
while mque and mque[-1] < nums[i]: # 若当前元素比队头元素大,弹出队头
mque.pop()
mque.append(nums[i]) # 当前元素入队
if i >= k-1: # 当经过的元素达到了滑动窗口宽度后
res.append(mque[0]) # 将队尾最大的元素保存到结果中,该元素即为当前窗口的最大元素
if mque and mque[0] == nums[i-k+1]: # 只有当窗口滑过该元素且该元素位于队尾时
mque.popleft() # 弹出队尾的当前窗口最大元素
return res
# 时间复杂度 O(n)
# 空间复杂度 O(k) 辅助队列的长度
347. 前 K 个高频元素
题目链接:347. 前 K 个高频元素 - 力扣(LeetCode)
文章讲解:代码随想录
解题卡点:第一次接触堆,很多东西都很模糊
本题思路:①要统计元素出现频率;②对频率排序;③找出前K个高频元素。①使用字典完成,②③使用优先级队列完成。
优先级队列,也被称为堆,实际是以list为表现形式的完全二叉树,树中每个结点的值都不小于 (或不大于) 其左右孩子的值。如果父结点大于等于左右子结点就是大顶堆,小于等于左右子结点就是小顶堆。入队时,每个元素带有某种优先级信息,要将其放在合适的位置上;出队时,删除优先级最高或最低的元素。普通队列可以看作以入队时间为优先级的优先级队列 (入队越早,优先级越高,越早出队)。
使用大顶堆还是小顶堆?小顶堆,因为要统计最大前K个元素,只有小顶堆每次将最小的元素弹出,最后堆里积累的才是前K个最大元素。
Tips:
heapq.heappush(heap, item),如果item是一个元组,默认将排序规则应用到第一个元素上。
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
import heapq
from collections import defaultdict
freq_count = defaultdict(int) # 统计数字出现的频率(次数)
for i in range(len(nums)):
freq_count[nums[i]] += 1
pri_que = [] # 以小顶堆创建优先级队列对频率进行排序,小顶堆大小为K
for num, freq in freq_count.items(): # 迭代完毕后,堆中只留下K个频率最大的元素
heapq.heappush(pri_que, (freq, num))
if len(pri_que) > k: # 如果堆的大小>K
heapq.heappop(pri_que) # 弹出顶端的低频元素
res = [0] * k
for i in reversed(range(0, k)): # 小顶堆先弹出最小元素,故倒序输出到结果数组
res[i] = heapq.heappop(pri_que)[1]
return res
# 时间复杂度 O(nlogk) k为堆的大小
# 空间复杂度 O(n)