LeetCode/solutions/239. Sliding Window Maximum.md
2020-02-15 14:24:14 +08:00

131 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# [239. Sliding Window Maximum](https://leetcode.com/problems/sliding-window-maximum/)
# 思路
给定一个数组和一个窗口大小k让窗口从左往右滑动返回每次窗口内的数字的最大值。
## 思路一
此题最容易想到的就是利用插入、删除和找到最值都不算慢的数据结构将窗口内的元素维护在这个数据结构中。所以我们可以利用类似搜索二叉树的结构例如STL中的map、set和multiset等但是由于有重复元素所以我们可以用multiset。
有几个注意点:
* multiset默认是从小到大排序所以最大值为最后一个元素即`*st.rbegin()`或`*prev(st.end())`
* 我们定义multiset的时候可以传入`greater<int>`类使从大到小排序,这样最大值为`*st.begin()`。
* 由于有重复数字,但每次只想删除一个,而 `erase(val)` 是将所有val都删掉所以我们只能提供一个迭代器代表一个确定的删除位置即用`erase(find(val))`删除。
由于插入删除都是对数级别所以总的时间复杂度为O(nlogk)额外的空间开辟就是multiset为O(k)。
## 思路二、双向取max
当k比较大的时候思路一的时间复杂度就显得有点高了此题还有两个O(n)复杂度的思路,即思路二和思路三。
用一个例子来说明: nums = [2,1,3,4,6,3,8,9,10,12,56], k = 4
```
1. 将数组根据窗口大小换分成若干块最后一块可能不足k
2, 1, 3, 4 | 6, 3, 8, 9 | 10, 12, 56
2. 从左到右记录到目前位置的最大值,注意每一块分别计算:
left_max[] = 2, 2, 3, 4 | 6, 6, 8, 9 | 10, 12, 56
3. 类似的,从右到左记录到目前位置的最大值,注意每一块分别计算:
right_max[] = 4, 4, 4, 4 | 9, 9, 9, 9 | 56, 56, 56
4. 现在若某个滑动窗口最右元素为nums[i],那么这个窗口内的最大值就为 max(left_max[i], right_max[i-k+1]);
```
简单证明一下,根据当前窗口是否刚好落在之前划分的某一块,可分为两种情况:
1. 若是,即`(i + 1) % k == 0`,那么`left_max[i]`和`right_max[i-k+1])`相等,均等于窗口内最大值;
2. 若不是,即窗口会横跨某相邻两块的交界线,这个交界线将窗口划分成左右两个部分,`left_max[i]`即右部分最大值而`right_max[i-k+1])`为左部分最大值。
时间复杂度O(n)空间复杂度O(n)
## 思路三、单调队列
还有一个O(n)的比较难想的思路需要用到双向队列deque。
核心思想是我们**不把窗口内所有元素都送入deque而是只将有可能成为最大值的元素的下标送入deque**。从左往右滑动窗口若窗口即将把nums[i]包含进来,
1. 首先,若队首元素下标小于`i - k`,即在窗口之外了,所以应该删除队首元素;
2. 然后,由于我们仅保留有可能成为最大值的元素(的下标),所以我们应该从**队尾**开始不断去掉比nums[i]小的那些元素的下标因为只要窗口内有nums[i],那么去掉的这些元素就不可能成为最大值。
3. 最后我们将nums[i](的下标)送入队尾。
因此,按照上述过程维护的队列里面的元素是单调递减的,队首的元素即每次窗口内的最大值。
这里需要掌握几个修改deque的操作
* `push_back`:从队尾加入队列;
* `push_front`:从队首加入队列;
* `pop_back`:删除队尾元素;
* `pop_front`:删除队首元素。
时间复杂度O(n)空间复杂度O(n)
# C++
## 思路一
``` C++
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(nums.empty()) return {};
int n = nums.size();
vector<int>res(n - k + 1);
multiset<int>st; // 从小到大排序
//multiset<int, greater<int>>st; // 从大到小排序
for(int i = 0, j = 0; i < n; i++){
if(i >= k) st.erase(st.find(nums[i-k])); // 不能st.erase(nums[i-k])
st.insert(nums[i]);
if(i >= k-1) res[j++] = *prev(st.end()); // 或st.rbegin()
}
return res;
}
};
```
## 思路二
``` C++
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if(nums.empty()) return {};
int n = nums.size();
vector<int>res(n - k + 1);
vector<int>left_max(n), right_max(n);
int maximum = INT_MIN;
for(int i = 0; i < n; i++)
left_max[i] = maximum = (i % k == 0) ? nums[i] : max(maximum, nums[i]);
maximum = nums.back();
for(int i = n-1; i >= 0; i--)
right_max[i] = maximum = ((i + 1) % k == 0) ? nums[i] : max(maximum, nums[i]);
for(int i = k-1, j = 0; i < n; i++)
res[j++] = max(left_max[i], right_max[i-k+1]);
return res;
}
};
```
## 思路三
``` C++
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int>res;
deque<int>win;
for(int i = 0; i < nums.size(); i++){
if (!win.empty() && win.front() == i - k) win.pop_front();
while(!win.empty() && nums[win.back()] <= nums[i]) win.pop_back();
win.push_back(i);
if(i >= k - 1) res.push_back(nums[win.front()]);
}
return res;
}
};
```