|
| 1 | +# 单调数据结构 |
| 2 | + |
| 3 | +## 1. 单调栈 |
| 4 | + |
| 5 | +### 1.1 原理 |
| 6 | + |
| 7 | +顾名思义,单调栈即元素单调递增或递减排列的栈。我们可以**通过单调栈求元素的左(右)边第一个比他大(小)的元素**。 |
| 8 | + |
| 9 | +例如我们想求数组里每个元素的左边第一个比它小的元素,我们可以维护一个严格单调增的栈,我们从前往后遍历原数组,设当前元素为num: |
| 10 | + |
| 11 | +* 我们不断去掉栈顶元素直到栈空或者栈顶元素小于num,此时栈顶元素就是num的左边第一个比它小的元素,记录结果,然后将num入栈。 |
| 12 | + |
| 13 | +整个过程的复杂度为 O(n) ,因为所有元素只会进栈出栈一次。 |
| 14 | + |
| 15 | +### 1.2 代码 |
| 16 | + |
| 17 | +``` C++ |
| 18 | +vector<int> PreviousSmallerElement(vector<int>& nums) { |
| 19 | + /* |
| 20 | + 求数组里每个元素的左边第一个比它小的元素,若找不到用-1填充 |
| 21 | + */ |
| 22 | + vector<int> res(nums.size()); |
| 23 | + stack<int> monoStack; |
| 24 | + for (int i = 0; i < nums.size(); i++) { |
| 25 | + while (!monoStack.empty() && monoStack.top() >= nums[i]) |
| 26 | + monoStack.pop(); |
| 27 | + |
| 28 | + res[i] = monoStack.empty() ? -1 : monoStack.top(); |
| 29 | + monoStack.push(nums[i]); |
| 30 | + } |
| 31 | + return res; |
| 32 | +} |
| 33 | +``` |
| 34 | +
|
| 35 | +### 1.3 相关题目 |
| 36 | +
|
| 37 | +LeetCode上面可以用单调栈解决的题还挺多的: |
| 38 | +* [42. Trapping Rain Water](https://leetcode.com/problems/trapping-rain-water/) |
| 39 | +* [84. Largest Rectangle in Histogram](https://leetcode.com/problems/largest-rectangle-in-histogram/) |
| 40 | +* [85. Maximal Rectangle](https://leetcode.com/problems/maximal-rectangle/) |
| 41 | +* [456. 132 Pattern](https://leetcode.com/problems/132-pattern/) |
| 42 | +* [496. Next Greater Element I](https://leetcode.com/problems/next-greater-element-i/) |
| 43 | +* [503. Next Greater Element II](https://leetcode.com/problems/next-greater-element-ii/) |
| 44 | +* [739. Daily Temperatures](https://leetcode.com/problems/daily-temperatures/) |
| 45 | +* [901. Online Stock Span](https://leetcode.com/problems/online-stock-span/) |
| 46 | +
|
| 47 | +
|
| 48 | +## 2. 单调队列 |
| 49 | +
|
| 50 | +### 2.1 原理 |
| 51 | +
|
| 52 | +与单调栈类似,单调队列即元素单调递增或递减排列的队列。我们可以**通过单调队列求滑动窗口的最值问题**。一般用双向队列`deque`实现。 |
| 53 | +
|
| 54 | +
|
| 55 | +例如我们想求大小为k的滑动窗口内部的最大值(LeetCode239),那么核心思路就是用一个`deque`存放有可能成为最大值的元素(的**下标**)。从左往右滑动窗口的过程中,若窗口即将把`nums[i]`包含进来, |
| 56 | +
|
| 57 | +1. 首先,若队首元素下标小于`i - k`,即队首在窗口之外了,所以应该删除队首元素; |
| 58 | +2. 然后,由于我们仅保留有可能成为最大值的元素(的下标),所以我们应该从队尾开始不断去掉比`nums[i]`小的那些元素(的下标),因为只要窗口内有`nums[i]`,那么去掉的这些元素就不可能成为最大值。 |
| 59 | +3. 最后,我们将`nums[i]`(的下标)送入队尾。 |
| 60 | +
|
| 61 | +因此,按照上述过程维护的队列里面的元素是单调递减的,队首的元素即每次窗口内的最大值。 |
| 62 | +
|
| 63 | +每个元素入队出队一次,时间复杂度O(n),且仅遍历一次数组。 |
| 64 | +
|
| 65 | +> 滑动窗口最值问题还有一个同样O(n)的思路,具体见[239题解](../../solutions/239.%20Sliding%20Window%20Maximum.md)思路二。 |
| 66 | +
|
| 67 | +### 2.2 代码 |
| 68 | +``` C++ |
| 69 | +// Leetcode 239 |
| 70 | +vector<int> maxSlidingWindow(vector<int>& nums, int k) { |
| 71 | + vector<int>res; |
| 72 | + deque<int>win; |
| 73 | +
|
| 74 | + for(int i = 0; i < nums.size(); i++){ |
| 75 | + if (!win.empty() && win.front() == i - k) win.pop_front(); |
| 76 | + |
| 77 | + while(!win.empty() && nums[win.back()] <= nums[i]) win.pop_back(); |
| 78 | + |
| 79 | + win.push_back(i); |
| 80 | + |
| 81 | + if(i >= k - 1) res.push_back(nums[win.front()]); |
| 82 | + } |
| 83 | + return res; |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +### 2.3 相关题目 |
| 88 | + |
| 89 | +* [239. Sliding Window Maximum](https://leetcode.com/problems/sliding-window-maximum/) |
| 90 | + |
| 91 | + |
| 92 | + |
0 commit comments