[LeetCode] Sliding Window Median 滑動窗口中位數


 

Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.

Examples:

[2,3,4] , the median is 3

[2,3], the median is (2 + 3) / 2 = 2.5

Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Your job is to output the median array for each window in the original array.

For example,
Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.

Window position                Median
---------------               -----
[1  3  -1] -3  5  3  6  7       1
 1 [3  -1  -3] 5  3  6  7       -1
 1  3 [-1  -3  5] 3  6  7       -1
 1  3  -1 [-3  5  3] 6  7       3
 1  3  -1  -3 [5  3  6] 7       5
 1  3  -1  -3  5 [3  6  7]      6

Therefore, return the median sliding window as [1,-1,-1,3,5,6].

Note:
You may assume k is always valid, ie: 1 ≤ k ≤ input array's size for non-empty array.

 

這道題給了我們一個數組,還是滑動窗口的大小,讓我們求滑動窗口的中位數。我想起來之前也有一道滑動窗口的題Sliding Window Maximum,於是想套用那道題的方法,可以用deque怎么也做不出,因為求中位數並不是像求最大值那樣只操作deque的首尾元素。后來看到了史蒂芬大神的方法,原來是要用一個multiset集合,和一個指向最中間元素的iterator。我們首先將數組的前k個數組加入集合中,由於multiset自帶排序功能,所以我們通過k/2能快速的找到指向最中間的數字的迭代器mid,如果k為奇數,那么mid指向的數字就是中位數;如果k為偶數,那么mid指向的數跟前面那個數求平均值就是中位數。當我們添加新的數字到集合中,multiset會根據新數字的大小加到正確的位置,然后我們看如果這個新加入的數字比之前的mid指向的數小,那么中位數肯定被拉低了,所以mid往前移動一個,再看如果要刪掉的數小於等於mid指向的數(注意這里加等號是因為要刪的數可能就是mid指向的數),則mid向后移動一個。然后我們將滑動窗口最左邊的數刪掉,我們不能直接根據值來用erase來刪數字,因為這樣有可能刪掉多個相同的數字,而是應該用lower_bound來找到第一個不小於目標值的數,通過iterator來刪掉確定的一個數字,參見代碼如下:

 

解法一:

class Solution {
public:
    vector<double> medianSlidingWindow(vector<int>& nums, int k) {
        vector<double> res;
        multiset<double> ms(nums.begin(), nums.begin() + k);
        auto mid = next(ms.begin(), k /  2);
        for (int i = k; ; ++i) {
            res.push_back((*mid + *prev(mid,  1 - k % 2)) / 2);        
            if (i == nums.size()) return res;
            ms.insert(nums[i]);
            if (nums[i] < *mid) --mid;
            if (nums[i - k] <= *mid) ++mid;
            ms.erase(ms.lower_bound(nums[i - k]));
        }
    }
};

 

上面的方法用到了很多STL內置的函數,比如next,lower_bound啥的,下面我們來看一種不使用這些函數的解法。這種解法跟Find Median from Data Stream那題的解法很類似,都是維護了small和large兩個堆,分別保存有序數組的左半段和右半段的數字,保持small的長度大於等於large的長度。我們開始遍歷數組nums,如果i>=k,說明此時滑動窗口已經滿k個了,再滑動就要刪掉最左值了,我們分別在small和large中查找最左值,有的話就刪掉。然后處理增加數字的情況(分兩種情況:1.如果small的長度小於large的長度,再看如果large是空或者新加的數小於等於large的首元素,我們把此數加入small中。否則就把large的首元素移出並加入small中,然后把新數字加入large。2.如果small的長度大於large,再看如果新數字大於small的尾元素,那么新數字加入large中,否則就把small的尾元素移出並加入large中,把新數字加入small中)。最后我們再計算中位數並加入結果res中,根據k的奇偶性來分別處理,參見代碼如下:

 

解法二:

class Solution {
public:
    vector<double> medianSlidingWindow(vector<int>& nums, int k) {
        vector<double> res;
        multiset<int> small, large;
        for (int i = 0; i < nums.size(); ++i) {
            if (i >= k) {
                if (small.count(nums[i - k])) small.erase(small.find(nums[i - k]));
                else if (large.count(nums[i - k])) large.erase(large.find(nums[i - k]));
            }
            if (small.size() <= large.size()) {
                if (large.empty() || nums[i] <= *large.begin()) small.insert(nums[i]);
                else {
                    small.insert(*large.begin());
                    large.erase(large.begin());
                    large.insert(nums[i]);
                }
            } else {
                if (nums[i] >= *small.rbegin()) large.insert(nums[i]);
                else {
                    large.insert(*small.rbegin());
                    small.erase(--small.end());
                    small.insert(nums[i]);
                }
            }
            if (i >= (k - 1)) {
                if (k % 2) res.push_back(*small.rbegin());
                else res.push_back(((double)*small.rbegin() + *large.begin()) / 2);
            }
        }
        return res;
    }
};

 

類似題目:

Find Median from Data Stream

Sliding Window Maximum

 

參考資料:

https://discuss.leetcode.com/topic/74905/c-o-n-logk-using-two-std-set

https://discuss.leetcode.com/topic/75160/easy-to-understand-clean-java-code

https://discuss.leetcode.com/topic/74963/o-n-log-k-c-using-multiset-and-updating-middle-iterator

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM