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:
- How about using a data structure such as deque (double-ended queue)?
- The queue size need not be the same as the window’s size.
- 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; } };
類似題目:
Longest Substring with At Most Two Distinct Characters
參考資料:
https://leetcode.com/problems/sliding-window-maximum/