1. 程式人生 > >【佇列】滑動視窗的最大值序列,帶max函式的佇列

【佇列】滑動視窗的最大值序列,帶max函式的佇列

視窗即佇列,本質是一樣的。

面試題59-1:滑動視窗的最大值序列

給定一個數組和滑動視窗的大小,請找出所有滑動窗口裡的最大值。例如,如果輸入陣列{2, 3, 4, 2, 6, 2, 5, 1}及滑動視窗的大小3,那麼一共存在6個滑動視窗,它們的最大值分別為{4, 4, 6, 6, 6, 5}。

用雙端佇列儲存最大值的下標,雙端佇列在視窗滑動過程中變化更新,其隊頭元素表示當前視窗中最大值的下標。

其實可以想象成一個年輕人和老年人比較的模型,年輕人就是陣列中靠後的數字,老年人就是靠前的數字,滑動視窗右邊滑入就相當於年輕人出生,滑動視窗的大小就是人的壽命,滑動視窗左邊滑出就是老年人死亡。而需要的雙端佇列正是記錄視窗中“最優秀的人”,用數字表示優秀的程度,年輕人比老年人數字高,那麼這個老年人在整個過程中都要受制於年輕人,因為這個年輕人已經出生了,他在視窗中的停留時間會比老年人的剩餘壽命長,這時就要把這樣的老年人移除雙端佇列。

而如果出生了不那麼優秀的年輕人,一樣要保留下來,因為它的剩餘壽命更長,待老年人從滑動視窗滑出後還是可能成為最優秀的(數字最大的)。但同樣,接下來如果出生了更優秀的人(更年輕),就要把剛剛的年輕人(相比這個他就是老年人)移除了。

#include<bits/stdc++.h>
using namespace std;

//輸入陣列和視窗大小,輸出滑動視窗的最大值陣列 
vector<int> maxInWindows(const vector<int>& num, unsigned int size) {
	vector<int> maxInWindows;
//滑動視窗的最大值陣列 if(num.size() >= size && size >= 1) {//陣列要比視窗大,視窗要不少於1 deque<int> index;//雙端佇列,用於記錄視窗中可能的最大值所在陣列中下標 //視窗初始狀態下會覆蓋陣列的前size個位置 for(unsigned int i = 0; i < size; ++i) { //如果新的這個數字比視窗中的一些數字大 //注意是比尾部的一些數字大 //比如[4213]...,先進4; //遇到2比尾部4小但在滑動中可能成為最大,尾插2 //遇到1比尾部2小但在滑動中可能成為最大,尾插1
//遇到3比尾部1大,因為只要1還在窗口裡3一定在 //所以一定輪不到1做最大,把1尾刪 //同理,接著遇到2,把2尾刪 //接著遇到4,3比4小,4劃出去了3可能還是最大 while(!index.empty() && num[i] >= num[index.back()])//等於的時候也刪不影響,相當於用大的遊標了 index.pop_back(); //尾部插入是一定會插入的,就像前面的分析過程 //因為不可能根據前面的老數字判斷年輕的自己沒有成為最大的可能 //只可能在下次for迴圈中看到更年輕且更大的數字,才能斷定自己沒有可能做最大了 index.push_back(i); } //前面初始化判斷過了0~size-1位置,接下來是滑動過程 //即從size位置到最後的num.size()-1陣列尾 for(unsigned int i = size; i < num.size(); ++i) { //在每次滑動之前寫入當前視窗的最大值 //即下標為index隊頭的陣列元素 maxInWindows.push_back(num[index.front()]); //視窗向前滑動(沒有任何程式碼),表現在接下來要使用i的值了 //或者可以說for裡的++i就是視窗向前滑動1 //但這樣也不好理解上一句是"滑動之前寫入" //滑動後,用同樣的邏輯調整存最大值下標的雙端佇列 while(!index.empty() && num[i] >= num[index.back()]) index.pop_back(); //如果當前雙端佇列的最大值下標不超過當前下標減去滑動視窗大小 if(!index.empty() && index.front() <= (int) (i - size)) index.pop_front();//就說明這個最大值剛好滑出來了,將其刪除 //和前面類似地,將當前位置的下標壓入 //只有後續的年輕數字才有淘汰自己的資格 index.push_back(i); } //最後一次滑動還沒寫入,將其最大值寫入 maxInWindows.push_back(num[index.front()]); } return maxInWindows;//返回滑動視窗的最大值陣列 } int main() { int num[] = { 2, 3, 4, 2, 6, 2, 5, 1 }; vector<int> vPut; for(unsigned int i=0;i<sizeof(num)/sizeof(int);i++){ vPut.push_back(num[i]); } vector<int> vGet=maxInWindows(vPut,3); for(vector<int>::const_iterator cit=vGet.cbegin();cit!=vGet.cend();cit++){ cout<<*cit<<"\t";//4 4 6 6 6 5 } return 0; }

面試題59-2:帶max函式的佇列

注意,這題在作者的GitHub上的題目描述寫錯了,寫成了59-1即上面那題的描述了。

請定義一個佇列並實現函式max得到佇列裡的最大值,要求函式max、push_back和pop_front的時間複雜度都是O(1)。

在入隊時賦予一個下標值(就像註冊使用者給個自增的id),這樣就可以用和59-1一樣的思路去解決了。入隊就相當於有元素滑進視窗,出隊就相當於有元素滑出視窗。前者需要判斷要刪除的老元素,後者需要判斷當前窗口裡的最大值有沒有離開視窗。

#include<bits/stdc++.h>
using namespace std;

//帶max函式的佇列模板類 
template<typename T> class QueueWithMax {
	private:
		//這個自定義佇列的元素結點結構體
		struct InternalData { 
			T number;//元素的值,泛型型別 
			int index;//插入時被賦予的下標 
		};
		deque<InternalData> data;//存佇列實際內容的雙端佇列 
		deque<InternalData> maximums;//存最大值的雙端佇列 
		int currentIndex;//接下來要插入元素應該賦予的下標 

	public:
		//構造器 
		QueueWithMax() : currentIndex(0) {//初始化下標值為0 
		}

		//入隊,相當於上一題裡面滑動視窗滑入一個數字 
		void push_back(T number) {
			//和老數字比較,把雙端佇列裡不比他大的老元素都刪掉 
			while(!maximums.empty() && number >= maximums.back().number)
				maximums.pop_back();
 
			InternalData internalData = { number, currentIndex };//建立元素結點
			data.push_back(internalData);//進入存元素的佇列 
			maximums.push_back(internalData);//也進入最大值佇列 

			++currentIndex;//id自增1,用來給下一個插入的元素用 
		}

		//出隊,相當於滑出視窗,不過這裡可以視為視窗從左側內陷1造成的 
		void pop_front() {
			if(maximums.empty())//佇列為空不能出隊 
				throw new exception();//queue is empty
			
			//這個就比上一題的滑出容易多了,直接判斷是不是當前最大 
			if(maximums.front().index == data.front().index)
				maximums.pop_front();//如果是,就把那個當前最大也拿掉(從隊頭) 

			data.pop_front();//出隊 
		}

		//獲取最大值
		//尾接const表示成員函式隱含傳入的this指標為const指標
		//即在這個成員函式內不能修改非mutable成員 
		T max() const {
			if(maximums.empty())//為空就沒有最大值 
				throw new exception();//queue is empty
			//存最大值的雙端佇列的隊頭元素的number域 
			return maximums.front().number;
		}
};

int main() {
	QueueWithMax<int> queue;
	queue.push_back(2);
	cout<<queue.max()<<endl;//2
	queue.push_back(4);
	cout<<queue.max()<<endl;//4
	queue.push_back(3);
	cout<<queue.max()<<endl;//4
	queue.push_back(2);
	cout<<queue.max()<<endl;//4
	queue.pop_front();
	cout<<queue.max()<<endl;//4
	queue.pop_front();
	cout<<queue.max()<<endl;//3
	return 0;
}