任務
150. 逆波蘭表示式求值
思路
用棧儲存運算元,遇到運算子,將運算元彈出並處理。注意除法的邏輯
class Solution:
def evalRPN(self, tokens: List[str]) -> int:
s = []
from operator import add, sub, mul
op_dic = {'+':add,'-':sub }
for i in range(len(tokens)):
if tokens[i] not in {'+','-','*','/'}:
s.append(int(tokens[i]))
elif tokens[i] == '+':
op2 = s.pop()
op1 = s.pop()
s.append(add(op1,op2))
elif tokens[i] == '-':
op2 = s.pop()
op1 = s.pop()
s.append(sub(op1,op2))
elif tokens[i] == '*':
op2 = s.pop()
op1 = s.pop()
s.append(mul(op1,op2))
elif tokens[i] == '/':
op2 = s.pop()
op1 = s.pop()
val = op1//op2 if op1 * op2 > 0 else -(abs(op1) // abs(op2))# 向0取整
s.append(val)
return s[0]
239. 滑動視窗最大值
給你一個整數陣列 nums,有一個大小為 k 的滑動視窗從陣列的最左側移動到陣列的最右側。你只可以看到在滑動視窗內的 k 個數字。滑動視窗每次只向右移動一位。
返回 滑動視窗中的最大值
思路
額外維護這樣一個滑動視窗(雙端佇列結構),因此該結構需要保證:某一時刻,雙端佇列中頭結點的值為最大值。
邏輯上,雙端佇列維護的是什麼資訊呢?
- 如果依次擴充視窗,誰會成為最大值的資訊。
- 如果不再擴充而是依次縮減滑動視窗,誰會成為最大值的資訊。
當陣列中的視窗往右擴時,為了維護上述結構,需要佇列中將它之前比它小的數都移除出去。這個是比較好想到的。
更深入的思考:為什麼新的數進來後可以讓之前比它小的數(或者相等的數)彈出呢?因為這個彈出的數再也不可能成為最大值了,我的新數比你大還比你晚過期,那麼就不需要你這個舊的小值了。也即是,滑動視窗實際上維護的資訊是兩個維度,數字越新越大越優先。按照上述邏輯,實際上,雙端佇列每一時刻都是單調遞減的。那麼縮減呢?只有當前要縮減的元素沒有在之前擴充時處理過,才縮減,否則已經被處理過了。
完整的演算法如下
- 擴充:只要比當前要入隊的數小,則從視窗中彈出
- 縮減:如果和頭節點的數一樣(舊的最大值),則彈出。否則當前需要處理的數就是又舊又不是之前的最大值的數,擴充時已經被處理過了。
- 最大值:隊首元素
class MyWindow:
def __init__(self):
self.queue = deque()
def push(self,val):
while(self.queue and val > self.queue[-1]): #只要比當前要入隊的數小,則從視窗中彈出
self.queue.pop()
self.queue.append(val) #將當前值放入滑動視窗
def pop(self,val):
if(self.queue and self.queue[0] == val):
self.queue.popleft()
def getMaxValue(self)->int:
return self.queue[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
res = []
window = MyWindow()
for i in range(k):
window.push(nums[i])
res.append(window.getMaxValue())
size = len(nums)
for i in range(k,size):
window.pop(nums[i-k])
window.push(nums[i])
maxV = window.getMaxValue()
res.append(maxV)
return res
347.前 K 個高頻元素
思路
堆(優先順序佇列)
這裡只考慮優先順序佇列的功能,暫時不考慮實現(上濾下濾等),以小頂堆為例,即在加入和刪除元素的過程中,堆頂元素保持最小值。
我們用dic來統計頻率,然後利用小頂堆依次新增,達到k個後,再依次新增和彈出最小元素,保證最大的k個元素均在小頂堆中,即可完成。
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
import heapq
import collections
dic = collections.defaultdict(int)
for i in range(len(nums)):
dic[nums[i]] += 1
pq = [] #預設小頂堆
for key,v in dic.items():
heapq.heappush(pq,(v,key))
if len(pq) > k: #後續每次新加入元素,滿足堆的結構後,彈出堆頂元素(最小值)
heapq.heappop(pq)
#此時pq中的元素為最大的k個元素,pq的堆頂為這k個內最小的元素
res = [0]*k
for i in range(k-1,-1,-1): #逆序賦值,保證次數單調遞減,val的次數越多的越靠前
val = heapq.heappop(pq)[1]
res[i] = val
return res
心得體會
複習了棧的應用,學習了滑動視窗(雙端佇列)和優先順序佇列。注意後兩種結構的思路和應用。
- 棧的應用 相鄰項之間的消除
- 滑動視窗 與之前的陣列中那道題不同,本題是自定義了一種特殊功能的資料結構,保證了可以很快取出極值,並隨著視窗的滑動,這個性質保持不變。
- 優先順序佇列 同樣利用元素的插入刪除,堆結構性質的一致性,保證棧頂為極值,解決topK問題。