单调栈和单调队列
看了一下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
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
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
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
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
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
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)
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 };
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

浙公网安备 33010602011771号