队列
隊列
隊列是一種“先進先出”的線性數據結構。可以從右端插入元素,左端彈出元素。所以第一進去的元素必然第一個出來。
上圖就是一個隊列,隊列里面有三個元素。外面有兩個元素,其中1 號元素剛從隊列里面出來,6 號元素是要進到隊列里面。
了解了隊列的基本原理,我們們也可以用數組來模擬隊列:
- 定義數組和隊列首尾位置,l為隊首, r為隊尾,一開始隊列是空的 : int queue[SIZE], l = 1, r = 0;
- 入隊列 x : queue[++r] = x
- 訪問隊首元素: queue[l]
- 出隊列 : l++;
訪問隊列元素的時候,要提前判斷一下隊列里面有沒有元素,如果有才能返回隊列元素。另外也可以直接計算出來隊列里面的元素個數:r-l+1.
循環隊列
如果直接用數組模擬隊列的話,隊尾指針不斷增大,隊首指針不斷增大,所以會浪費很多的空間。
想要解決這個問題,可以把隊列想象成一個環。即下標為 0 的位置是最后一個位置的后繼。兩個指針都在環里面移動,這樣就會復用以前的空間了。
假設現在隊列一共有 size 個空間,尾指針的位置是 pos, 如果添加一個元素,尾指針的位置就會變成 pos + 1 % size,
用循環隊列需要注意的一點就是要保證隊列元素的個數在任意時刻都不大于環的長度。
單調隊列
在單調隊列中,元素的值是有順序的,可以是單調遞增的,也可以是單調遞減的。基于這個性質,我們可以解決一些問題。
例題:
劍指 Offer 59 - I. 滑動窗口的最大值
給定一個數組 nums 和滑動窗口的大小 k,請找出所有滑動窗口里的最大值。
示例:
輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 輸出: [3,3,5,5,6,7] 解釋: 滑動窗口的位置 最大值--------------- -----[1 3 -1] -3 5 3 6 7 31 [3 -1 -3] 5 3 6 7 31 3 [-1 -3 5] 3 6 7 51 3 -1 [-3 5 3] 6 7 51 3 -1 -3 [5 3 6] 7 61 3 -1 -3 5 [3 6 7] 7首先使用暴力的方法,代碼如下。
int n = nums.size(); // 得到數組的長度。 for (int i = 0; i < n; ++i){ // 枚舉右端點的位置int ans = nums[i]; // 初始化區間答案if (i >= k - 1){ // 判斷右區間是不是合法。 [0, k-1] 正好 k 個元素。for (int j = i - k + 1; j <= i; j++) // 確定左區間的位置,遍歷,然后找最大值。ans = max(ans, nums[j]);} }這種暴力的做法,時間復雜度是 O(n?k)O(n*k)O(n?k) 的,n 是數組的長度, k 是區間的長度,所以 k 越大時間復雜度就越大。
下面來討論一下更加優雅的做法,那就是單調隊列:
首先強調一下,單調隊列的基礎不是一個簡單的隊列,它類似雙端隊列,即左右都可以出隊。
單調隊列的步驟:
1、初始化一個雙端隊列,也就是用數組que來模擬隊列的操作,隊列里面存放的是每個數的位置。初始化隊列的隊首隊尾的位置 left = 1, right = 0 , 代表現在隊列里面沒有元素,只有 right >= left 的時候,隊列才有元素。 que[left] que[right] 分別代表滑動窗口的左右位置。
2、滑動窗口的右端點向右移動一位,就是新增一個元素。拿新增的元素不斷的和隊尾代表的元素比較,如果新元素大于等于隊尾代表的元素,那么隊尾要彈出一個元素。直到隊列沒有元素或者新元素大于隊尾所代表的元素。然后 right++,把新元素的位置加入到隊列。 想一下這樣為什么可以,當滑動窗口右移一位,新增加一個元素y,y 大于左邊的一個元素x,在滑動窗口接下來的右移過程中 x 永遠不能成為最大值,至少 y 比 x 大,所以 x 就可以直接刪除了。
3、滑動窗口的左端點向右移動。每次要保持窗口里面的元素個數是 k 個,既然滑動窗口的右端點移動了,左端點也要移動。
4、得到答案。如果 right >= k-1, 代表前面有一個窗口的大小,那么就需要記一下答案,答案就是隊首所代表的元素。
5、一直循環 2 3 4,直到所有元素都添加進隊列。
當滑動窗口右移一位,加入一個元素 x 的時候。會不斷彈出來隊尾小于等于 x 的元素,所以 x 放在大于 x的元素的后面。加入的每個值都是這樣,那么隊列里面代表的元素是單調遞減的,隊首就代表最大的元素。
如果文字不能理解的話,可以看下面的圖再加深理解。
紅色方框里面就是隊列里面所代表的值。為了方便理解,直接把元素放到隊列里面了。實際上隊列里面放的是每個元素在數組中的位置。
加入數字 1, 隊列里面就一個數字 1.
加入數字 2, 隊尾元素小于 3, 把隊尾元素彈出,所以隊列里面元素只有一個 3
加入數字 -1, 隊尾元素大于 -1, 直接放到隊尾就可以了, 隊列里面的元素是 3 、-1,窗口最大值為 3.
加入數字 -3, 隊尾元素大于 -3, 也是直接放到隊尾。 隊列里面元素是 3、-1、-3, 窗口最大值是 3
加入數字 5, 隊列元素都小于 5, 隊列所有值依次彈出。然后 5 放到隊尾, 隊列元素是 5, 窗口最大值是 5
加入元素 3, 隊尾元素大于 3, 直接放入隊尾就可以, 隊列元素是 5、3, 窗口最大值是 5
加入數字 6, 隊列元素都小于 6, 隊列所有值依次彈出。然后 6 放到隊尾, 隊列元素是 6, 窗口最大值是 6
加入數字 7, 隊列元素都小于 7, 隊列所有值依次彈出。然后 7 放到隊尾, 隊列元素是 7, 窗口最大值是 7
上面的圖只是粗略的描述了一下過程,具體還是看下面的代碼來理解。結合下面的代碼,如果自己可以在紙上畫一個具體的流程出來。那就說明已經能夠理解了。
單調隊列的模板很好寫。 無外乎就是幾個步驟, 框架是死的,但是內容是活的。細節需要自己推敲。
上面的代碼就是最簡單的單調隊列代碼,其他的題都是在其基礎上修改。所以一定要把上面的代碼吃透了,理解其中每一句的意義。
訓練題目:
- 劍指 Offer 59 - II. 隊列的最大值
總結
- 上一篇: visio中公式太小_学SolidWor
- 下一篇: 医保的那些事儿