標籤:偶數 compare off class 數字 隊列 bsp 返回 為什麼
今天在網上刷了一道關於堆的題,感覺有所收穫。因為在這裡之前,之前從來沒有接觸過關於堆的題目。
題意:
給定一個包含 n 個整數的數組,和一個大小為 k 的滑動視窗,從左至右在數組中 滑動這個視窗,找到數組中每個視窗內的中位元。(如果數組個數是偶數,則在 該視窗排序數字後,返回第 N/2 個數字。)
範例:
對於數組 [1,2,7,8,5], 滑動大小 k = 3 的視窗時,返回 [2,7,7] 最初,視窗的數組是這樣的: [ | 1,2,7 | ,8,5] , 返回中位元 2; 接著,視窗繼續向前滑動一次。 [1, | 2,7,8 | ,5], 返回中位元 7; 接著,視窗繼續向前滑動一次。 [1,2, | 7,8,5 | ], 返回中位元 7;
最初看到這個題,想到的方法是暴力,將給定的視窗裡面數字從先到大排序,再去中間那個數就行了。但是,到後面發現逾時了。於是乎,在網上搜尋了相關解法,網上大多數使用的是用優先隊列來操作。基本思路:用兩個優先隊列來模仿大頂堆和小頂堆(關於大頂堆和小頂堆的含義,這裡不再詳細的解釋),然後操作兩個隊列裡面的資料,選出中位元。
1.解題思想
(1).優先隊列
用一個優先隊列模仿大頂堆,用來裝已經在視窗裡面的數位小的那一半,另一個隊列則是小頂堆,裝另一半。於是乎,剩下的那一個(沒有進入任何一個隊列)就是中位元
(2).最初的添加資料
我們假設兩個隊列在初始化都是空的,同時最初的時候,視窗的裡面什麼資料都沒有,並且將當前的中位元設定為即將進入視窗的那個數。所以我們預設,向視窗加入資料是從第二個開始。
當我們加入一個資料時,判斷它與當前的中位元(第一個資料作為中位元)的大小,如果它大於當前的中位元的話,則將它放入小頂堆;反之則放入大頂堆。放入過後(不論是大頂堆還是小頂堆),判斷大頂堆的size與小頂堆的size,如果大頂堆的size大於小頂堆的size,則將當前的中位元放入小頂堆,從大頂堆取出一個資料(poll)作為新的中位元;如果小頂堆的size - 1都大於大頂堆的size,那麼將當前的中位元放入大頂堆,從小頂堆中取出一個資料作為新的中位元。:
問題: 為什麼這裡大頂堆的size大於小頂堆size就調整,而小頂堆的size - 1大於大頂堆的size才調整呢?
我們先來看看題:如果數組個數是偶數,則在該視窗排序數字後,返回第 N/2 個數字。這裡說,如數字個數為偶數,則取 N/2。例如:視窗中有3個數字:1 2 3, 那中位元是 2,這個沒有爭議;但是如果視窗有4個數字:1 2 3 4 ,按照題意,選擇 N/2,則是2,也就是說當前大頂堆為:4,而小頂堆:1 2。所以在小頂堆的size - 1大於大頂堆的size才調整,因為如果不調整的話,當前的中位元與當前視窗的數位中位元不符合,因為視窗在這之前已經加入一個數字。
而加入一個數字,中位元有兩種結果:當兩個頂堆的size相等或者小頂堆的size - 大頂堆的size = 1時,中位元不變,因為兩個頂堆的size平衡(我們可以這樣認為,當前視窗中數字小的那一半在視窗的左邊,大的那一半放在視窗的右邊,而中位元在兩個頂堆中間);當大頂堆的size大於小頂堆的size,表示實際的中位元偏向了大頂堆,所以將當前的中位元放入小頂堆去,從大頂堆中取出最大值作為當前的中位元(大頂堆中的所有數字小於當前的中位元,小頂堆的所有數字大於當前中位元,所以將當前的數字放入大頂堆)。總之一句話,就是加入一個數字後,必須保證當前的中位元就是實際的中位元。
(3).後續的添加資料
首先我們按照最初的添加資料步驟中得出的中位元,根據前面的規則來添加資料。
當添加一個過後,我們必須在兩個頂堆中任意一個移除一個資料,這樣才能保證當前視窗顯示的數字。我們知道肯定移除當前視窗的第一個,怎麼移除呢?首先當前視窗的第一個的值,這個值與當前的中位元來比較,如果大於中位元,則這個值在小頂堆中,從小頂堆移除就是了;如果小於中位元,表示這個值在大頂堆中,從大頂堆中移除;當等於中位元時,則判斷當前小頂堆的size與大頂堆的size,如果大於大頂堆,則從小頂堆中取出一個資料作為新的中位元;反之,從大頂堆取出一個資料作為新的中位元。
問題:為什麼當移除那個值與當前的中位元相等,要這樣操作?
首先經過前一次添加資料,大頂堆的size與小頂堆的size要麼相等,要麼大頂堆的size - 小頂堆的.sze = 1。而這一次添加資料後,要麼加入大頂堆,要麼加入大頂堆,根據size判斷,當大頂堆的size >= 小頂堆的size,表示當前添加資料小於當前的中位元,則實際上的中位元偏向了大頂堆了,所以從大頂堆中取出一個資料作為新的中位元;反之也是這樣,從小頂堆中取出一個資料作為新的中位元也是這個道理。
(4).進一步的調整
如果在(3)中,移除之前,大頂堆的size - 小頂堆的.sze = 1,而且移除的資料在大頂堆中,那麼實際的中位元就偏向了小頂堆,所以需要進一步調整;同時如果,本來大頂堆的size 等於 小頂堆的.sze ,而且移除的資料在小頂堆中,那麼實際的中位元就偏向大頂堆。
2.代碼
說完瞭解釋,現在開始貼代碼
1 public class minComparator implements Comparator<Integer> { 2 public int compare(Integer a, Integer b) { 3 if (a > b) 4 return 1; 5 else if (a == b) 6 return 0; 7 else 8 return -1; 9 }10 }11 12 public class maxComparator implements Comparator<Integer> {13 public int compare(Integer a, Integer b) {14 if (a > b)15 return -1;16 else if (a == b)17 return 0;18 else19 return 1;20 }21 }22 23 public List<Integer> medianSlidingWindow(int[] nums, int k) {24 List<Integer> res = new ArrayList<Integer>();25 if (k == 0 || nums.length < k) {26 return res;27 }28 PriorityQueue<Integer> maxQueue = new PriorityQueue<>();//大頂堆29 PriorityQueue<Integer> minQueue = new PriorityQueue<>();//小頂堆30 int media = nums[0];31 //最初的添加資料32 for (int i = 0; i < k; i++) {33 if (media < nums[i]) {34 minQueue.offer(nums[i]);35 } else {36 maxQueue.offer(nums[i]);37 }38 if (maxQueue.size() > minQueue.size()) {39 minQueue.offer(media);40 media = maxQueue.poll();41 } else if (maxQueue.size() < minQueue.size() - 1) {42 maxQueue.offer(media);43 media = maxQueue.poll();44 }45 }46 res.add(media);47 //後續的添加資料48 for (int i = 0; i < nums.length; i++) {49 if (media < nums[i]) {50 minQueue.offer(nums[i]);51 } else {52 maxQueue.offer(nums[i]);53 }54 //移除當前視窗第一個值55 int old = nums[i - k];56 if (old == media) {57 if (minQueue.size() > maxQueue.size()) {58 media = minQueue.poll();59 } else {60 media = maxQueue.poll();61 }62 } else if (old < media) {63 maxQueue.remove(old);64 } else {65 minQueue.remove(old);66 }67 //進一步調整68 while (maxQueue.size() > minQueue.size()) {69 minQueue.offer(media);70 media = maxQueue.poll();71 }72 while (minQueue.size() < minQueue.size() - 1) {73 maxQueue.offer(media);74 media = minQueue.poll();75 }76 res.add(media);77 }78 return res;79 }
演算法-滑動視窗的中位元(堆)