[LeetCode] 239. Sliding Window Maximum 滑動窗口最大值


 

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. Return the max sliding window.

Example:

Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3
Output: [3,3,5,5,6,7] 
Explanation: 

Window position                Max
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 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       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

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

Follow up:
Could you solve it in linear time?

Hint:

  1. How about using a data structure such as deque (double-ended queue)?
  2. The queue size need not be the same as the window’s size.
  3. Remove redundant elements and the queue should store only elements that need to be considered.

 

這道題給定了一個數組,還給了一個窗口大小k,讓我們每次向右滑動一個數字,每次返回窗口內的數字的最大值。難點就在於如何找出滑動窗口內的最大值(這不廢話么,求得不就是這個),那么最狂野粗暴的方法就是每次遍歷窗口,找最大值唄,OJ 說呵呵噠,no way!我們希望窗口內的數字是有序的,但是每次給新窗口排序又太費時了,所以最好能有一種類似二叉搜索樹的結構,可以在 lgn 的時間復雜度內完成插入和刪除操作,那么使用 STL 自帶的 multiset 就能滿足我們的需求,這是一種基於紅黑樹的數據結構,可以自動對元素進行排序,又允許有重復值,完美契合。所以我們的思路就是,遍歷每個數字,即窗口右移,若超過了k,則需要把左邊界值刪除,這里不能直接刪除 nums[i-k],因為集合中可能有重復數字,我們只想刪除一個,而 erase 默認是將所有和目標值相同的元素都刪掉,所以我們只能提供一個 iterator,代表一個確定的刪除位置,先通過 find() 函數找到左邊界 nums[i-k] 在集合中的位置,再刪除即可。然后將當前數字插入到集合中,此時看若 i >= k-1,說明窗口大小正好是k,就需要將最大值加入結果 res 中,而由於 multiset 是按升序排列的,最大值在最后一個元素,我們可以通過 rbeng() 來取出,參見代碼如下:

 

解法一:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        multiset<int> st;
        for (int i = 0; i < nums.size(); ++i) {
            if (i >= k) st.erase(st.find(nums[i - k]));
            st.insert(nums[i]);
            if (i >= k - 1) res.push_back(*st.rbegin());
        }
        return res;
    }
};

 

我們也可以使用優先隊列來做,即最大堆,不過此時我們里面放一個 pair 對兒,由數字和其所在位置組成的,這樣我們就可以知道每個數字的位置了,而不用再進行搜索了。在遍歷每個數字時,進行 while 循環,假如優先隊列中最大的數字此時不在窗口中了,就要移除,判斷方法就是將隊首元素的 pair 對兒中的 second(位置坐標)跟 i-k 對比,小於等於就移除。然后將當前數字和其位置組成 pair 對兒加入優先隊列中。此時看若 i >= k-1,說明窗口大小正好是k,就將最大值加入結果 res 中即可,參見代碼如下:

 

解法二:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        priority_queue<pair<int, int>> q;
        for (int i = 0; i < nums.size(); ++i) {
            while (!q.empty() && q.top().second <= i - k) q.pop();
            q.push({nums[i], i});
            if (i >= k - 1) res.push_back(q.top().first);
        }
        return res;
    }
};

 

題目中的 Follow up 要求我們代碼的時間復雜度為 O(n)。提示我們要用雙向隊列 deque 來解題,並提示我們窗口中只留下有用的值,沒用的全移除掉。果然 Hard 的題目我就是不會做,網上看到了別人的解法才明白,解法又巧妙有簡潔,膜拜啊。大概思路是用雙向隊列保存數字的下標,遍歷整個數組,如果此時隊列的首元素是 i-k 的話,表示此時窗口向右移了一步,則移除隊首元素。然后比較隊尾元素和將要進來的值,如果小的話就都移除,然后此時我們把隊首元素加入結果中即可,參見代碼如下:

 

解法三:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        deque<int> q;
        for (int i = 0; i < nums.size(); ++i) {
            if (!q.empty() && q.front() == i - k) q.pop_front();
            while (!q.empty() && nums[q.back()] < nums[i]) q.pop_back();
            q.push_back(i);
            if (i >= k - 1) res.push_back(nums[q.front()]);
        }
        return res;
    }
};

 

類似題目:

Minimum Window Subsequence

Min Stack

Longest Substring with At Most Two Distinct Characters

Paint House II

 

參考資料:

https://leetcode.com/problems/sliding-window-maximum/

https://leetcode.com/problems/sliding-window-maximum/discuss/65936/My-Java-Solution-Using-PriorityQueue

https://leetcode.com/problems/sliding-window-maximum/discuss/65884/Java-O(n)-solution-using-deque-with-explanation

 

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


免責聲明!

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



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