单调栈和单调队列

看了一下lee215的公众号里关于单调栈的讲解,这里我把用到单调栈和单调队列的常见题总结一下。

单调栈可以维护找左右两侧第一个比它大/小的元素,进而维护以这个元素为最大/小值的最大区间。以单调递增栈为例,一个元素满足进栈条件时,栈顶的元素是它左侧第一个比它小的元素,而令它出栈的是它右侧第一个比它小的元素。

1. LeetCode 503

 1 # 出栈时维护版本
 2 class Solution:
 3     def nextGreaterElements(self, nums: List[int]) -> List[int]:
 4         n = len(nums)
 5         res, stack = [-1] * n, []
 6         for i in range(2 * n):
 7             idx = i % n
 8             while stack and nums[idx] > nums[stack[-1]]:
 9                 res[stack.pop()] = nums[idx]
10             stack.append(idx)
11         return res
12 
13 
14 # 进栈时维护版本
15 class Solution:
16     def nextGreaterElements(self, nums: List[int]) -> List[int]:
17         n = len(nums)
18         res, stack = [-1] * n, []
19         for i in range(2 * n - 1, -1, -1):
20             idx = i % n
21             while stack and nums[idx] >= nums[stack[-1]]:  # 注意这里是>=
22                 stack.pop()
23             if stack:
24                 res[idx] = nums[stack[-1]]
25             stack.append(idx)
26         return res
View Code

2. LeetCode 84

 1 # 一趟遍历同时找出左右两侧第一个小于的元素位置
 2 class Solution:
 3     def largestRectangleArea(self, heights: List[int]) -> int:
 4         res, stack = 0, []
 5         for idx, val in enumerate(heights + [0]):  # 尾部加上0让heights的元素都能弹出
 6             while stack and val < heights[stack[-1]]:
 7                 hei, left = heights[stack.pop()], stack[-1] if stack else -1
 8                 res = max(res, hei * (idx - left - 1))
 9             stack.append(idx)
10         return res
View Code

3. LeetCode 85

 1 class Solution:
 2     def maximalRectangle(self, matrix: List[List[str]]) -> int:
 3         if not matrix:
 4             return 0
 5         row, col = len(matrix), len(matrix[0])
 6         temp, res = [0] * col, 0
 7         for i in range(row):
 8             for j in range(col):
 9                 temp[j] = temp[j] + 1 if matrix[i][j] == "1" else 0
10             res = max(res, self.largestRectangleArea(temp))
11         return res
12     
13     def largestRectangleArea(self, heights: List[int]) -> int:
14         res, stack = 0, []
15         for idx, val in enumerate(heights + [0]):
16             while stack and val < heights[stack[-1]]:
17                 hei, left = heights[stack.pop()], stack[-1] if stack else -1
18                 res = max(res, hei * (idx - left - 1))
19             stack.append(idx)
20         return res
View Code

4. LeetCode 456

 1 class Solution:
 2     def find132pattern(self, nums: List[int]) -> bool:
 3         stack, a2 = [], -float("inf")
 4         for i in range(len(nums) - 1, -1, -1):
 5             if nums[i] < a2:
 6                 return True
 7             while stack and nums[i] > stack[-1]:
 8                 a2 = stack[-1]
 9                 stack.pop()
10             stack.append(nums[i])
11         return False
View Code

5. LeetCode 901

 1 # 思路是入栈的时候获取左侧第一个比它大的数,所以是一个单调递减栈
 2 # 方法一给每个到来的price都编了号,但是这种方法在数据量大的时候容易溢出
 3 # 方法二记录每个price左侧的span,不容易溢出
 4 
 5 
 6 class StockSpanner:  # 方法一
 7 
 8     def __init__(self):
 9         self.stack = []
10 
11     def next(self, price: int) -> int:
12         idx = self.stack[-1][0] + 1 if self.stack else 0
13         while self.stack and price >= self.stack[-1][1]:
14             self.stack.pop()
15         res = idx - (self.stack[-1][0] if self.stack else -1)
16         self.stack.append((idx, price))
17         return res
18 
19 
20 class StockSpanner:  # 方法二
21     def __init__(self):
22         self.stack = []
23 
24     def next(self, price: int) -> int:
25         span = 1
26         while self.stack and self.stack[-1][0] <= price:
27             span += self.stack.pop()[1]
28         self.stack.append((price, span))
29         return span
View Code

6. LeetCode 907

 1 class Solution:  # 思考对[1,1,1]这样的序列如果做到不重复统计的
 2     def sumSubarrayMins(self, A: List[int]) -> int:
 3         res, stack, Mod = 0, [], 10 ** 9 + 7  # stack里面存放下标和值
 4         for idx, val in enumerate(A + [0]):
 5             while stack and val <= stack[-1][1]:
 6                 right = idx - stack[-1][0]
 7                 cur_idx, cur_val = stack.pop()
 8                 left = cur_idx - (stack[-1][0] if stack else -1)
 9                 res = (res + cur_val * left * right) % Mod
10             stack.append((idx, val))
11         return res
12 
13 
14 class Solution(object):  # 这个解法是参考Solusion,思路很奇特,有兴趣可以去看下
15     def sumSubarrayMins(self, A: List[int]) -> int:
16         ans, dot, stack, MOD = 0, 0, [], 10 ** 9 + 7
17         for idx, val in enumerate(A):
18             count = 1
19             while stack and stack[-1][0] >= val:
20                 x, c = stack.pop()
21                 count += c
22                 dot -= x * c
23             stack.append((val, count))
24             dot += val * count
25             ans += dot
26         return ans % MOD
View Code

7. LeetCode 975

这道题的dp很好想,但是怎么在右侧找值最接近的位置是难点,在C++中,有Tree Map这样的结构可以很方便的通过二分找到,时间复杂度在$O(nlgn)$。但是python语言里面没有Tree Map之类的实现,那应该怎么去找呢?

排序+单调栈解法

 1 class Solution(object):
 2     def oddEvenJumps(self, A):
 3         N = len(A)
 4 
 5         def make(B):
 6             ans = [None] * N
 7             stack = []  # invariant: stack is decreasing
 8             for i in B:
 9                 while stack and i > stack[-1]:
10                     ans[stack.pop()] = i
11                 stack.append(i)
12             return ans
13 
14         B = sorted(range(N), key = lambda i: A[i])
15         oddnext = make(B)
16         B.sort(key = lambda i: -A[i])
17         evennext = make(B)
18 
19         odd = [False] * N
20         even = [False] * N
21         odd[N-1] = even[N-1] = True
22 
23         for i in xrange(N-2, -1, -1):
24             if oddnext[i] is not None:
25                 odd[i] = even[oddnext[i]]
26             if evennext[i] is not None:
27                 even[i] = odd[evennext[i]]
28 
29         return sum(odd)
View Code

C++ Tree map的解法

 1 class Solution {
 2 public:
 3     int oddEvenJumps(vector<int>& A) {
 4         map<int, int> m;
 5         vector<pair<bool, bool> > dp (A.size(), make_pair(false, false));
 6         dp[A.size() - 1] = make_pair(true, true);
 7         m[A.back()] = A.size() - 1;
 8         int res = 1;
 9         for (int i = A.size() - 2; i >= 0; --i) {
10             auto iter = m.lower_bound(A[i]);
11             if (iter != m.end())
12                 dp[i].first = dp[iter->second].second;
13             iter = m.upper_bound(A[i]);
14             if (iter != m.begin())
15                 dp[i].second = dp[prev(iter)->second].first;
16             m[A[i]] = i;
17             if (dp[i].first) ++res;
18         }
19         return res;
20     }
21 };
View Code

8. LeetCode 1130

9. LeetCode 1340

10. LeetCode 5180

单调队列的经典应用:定长区间的最值维护

 1 # monotonous queue + dp
 2 class Solution:
 3     def constrainedSubsetSum(self, nums: List[int], k: int) -> int:
 4         ans, deq = -float("inf"), deque()
 5         for i in range(len(nums)):
 6             cur = max(0, deq[0][1] if deq else 0) + nums[i]
 7             if deq and i - deq[0][0] == k:
 8                 deq.popleft()
 9             while deq and cur >= deq[-1][1]:
10                 deq.pop()
11             deq.append((i, cur))
12             ans = max(ans, cur)
13         return ans
View Code
posted @ 2020-03-27 14:34  wory  阅读(268)  评论(0)    收藏  举报