1. 簡述
假設有這樣一個擁有3個操作的隊列:
1. EnQueue(v):將v排入佇列中
2. DeQueue:使隊列中的對首元素刪除並返回此元素
3. MaxElement:返回隊列中的最大元素
請設計一個資料結構和演算法,讓MaxElement操作的時間複雜度儘可能的低。
2. 思路
起初沒仔細看,還以為與此前的自訂棧-pop-push-min-時間複雜度都為O(1) 是一樣的,後來才發現不是一回事,有差別的。對於棧來說,我們可以的入棧和出棧不會影響輔助數組內的情況,假設當前N個元素,(為了說明簡單,下標從1開始),輔助空間的F[1]記錄的是A[1,1]內的最值位置,F[2]記錄的是A[1,2]內的最值位置,···,F[N]記錄的是A[1,N]內的最值位置。在插入F[k+1]=A[F[k]]與A[k+1]兩者最值的位置,插入複雜度為O(1),在刪除第k+1個節點時,刪除A[k+1]和F[k+1],這並不影響F[1]-F[k],因此刪除的複雜度為O(1),取最值,假設當前N個元素,即返回A[F[N]]。
對於隊列來說,如果套用棧的輔助數組方法,假設當前有N個元素,下標從1開始,那麼F[1]記錄的是A[1,N]中的最值,F[2]記錄的是A[2,N]中的最值,···,F[N]記錄的是A[N,N]中的最值。當A[k+1]入隊時,需要更新F[1]到F[k],F[K+1]=K+1,因此插入的複雜度為O(N),當F[k]出隊時,刪除F[k]即可(每次出隊的都是第一個元素,實際上此時F[1]-F[k-1]已經出隊完畢了),因此刪除的複雜度為O(1)。取最值的複雜度是O(1)。
隊列與棧的區別很清楚了。編程之美上給了兩個答案,一個是構建最大堆,另一個是用兩個棧來實現。
最大堆的方法:
隊列本身要麼順序結構要麼連結結構,還那麼存。另外對於隊列每個元素構建一個節點(包含在隊列中的位置),這些節點構成一個最大堆,因此插入和刪除操作都要維護這個最大堆,時間複雜度都是O(LogN),取最大值的複雜度為 O(1)。
兩個棧的方法:
A棧,B棧,這兩個棧都是前面提到的pop-push-min複雜度都為O(1)的空間換時間的實現。
取最值:返回A棧的最值和B棧的最值相比後的最值。複雜度O(1)。
入隊操作:直接入到B棧中。複雜度O(1)。
出隊操作:如果A棧不為空白,直接A棧出棧,複雜度為O(1),如果A棧為空白,那麼將B棧內容逐個出棧並且逐個入棧到A中,然後A棧出棧,複雜度O(N),實際上是B棧的長度。
對於這種方法,如果對列的操作時,一連串的入棧,然後是一連串的出棧,那麼就是首先不停向B入棧,然後第一個出棧,B棧元素全壓入A棧,A出棧一個,這一步是N的複雜度,但是此後是不停的從A出棧,這都是O (1)的複雜度。還不錯呢。而且藉助了棧的代碼,方便實現。對於這樣的情景,就是只有第一個出棧的時候,要O(N),複雜度不是很均勻。對於每個元素來說,要麼入B棧,入A棧,從A棧彈出,即總體是3N,平均下來基本上是O(3),要不最大堆的O(LogN)是快了不少呢。
3. 參考
編程之美,3.7,隊列中取最大值操作問題