刷题总结 栈和队列:单调栈
一、原理
1.模式识别特征
数据结构:数组或字符串等有索引的顺序
问题:当数组形式的数据结构需要解决以下的索引-值配对的问题,则可以使用单调栈
①下一个更大元素 ②上一个更大元素
③下一个更小元素 ④上一个更小元素
2.栈和单调性分别体现在哪里?
单调栈的是一种用栈来记录元素用于配对最邻近的相对大小关系的方式。
问题1:栈记录了的哪些元素?
问题2:如何配对?
问题3:单调性作用?
答案1:索引-值配对中待配对的索引或待配对的值
答案2:栈内的索引与遍历到的当前元素i的值配对(正向抛出时)或栈内的值与遍历到的当前元素i的索引(反向入栈时)配对
答案3:方便顺序比大小并找到配对值的开始和终止位置
以下一个更大元素为例:
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
n = len(temperatures)
ans = [0] * n
stack = []
for i in range(n):
while stack and temperatures[i] > temperatures[stack[-1]]:
ch = stack.pop()
ans[ch] = i
# if stack: ans[i] = stack[-1]
stack.append(i)
return ans
for循环遍历,每遍历到一个位置i,i位置元素与栈内元素进行配对,
会遇到三种情况:
①元素i比栈内元素都小,i元素入栈
②元素i比栈内元素都大,栈清空,i元素入栈,
以上两种情况单调栈没有起到作用,单调栈的作用体现在第三种情况:
③栈内部分元素大于i,部分元素小于i:
以“中”代指元素i,“小”代指栈中比i小的元素,“大”代指栈中比i大的元素
状态:[大,小]→,中(当前元素i),
目标是找下一个大元素,栈中的小元素(的索引)与的下一个大元素就是当前元素i,即“中”,
那么单调栈抛出小元素,抛出后小元素的索引与i的值配对
单调栈用于解决索引-值配对的问题,因此,栈内元素的物理意义就是待配对的索引,
此外,反向遍历(for i in range(n - 1, -1, -1))也可以解决问题,反向遍历时,
状态:中(当前元素i),←[小,大]
单调栈抛出小,抛出后中的索引与大的值配对,即当前元素的索引与栈内大元素的值配对
3.单调栈的使用方式
(1)配对方式
配对方式有两种:
①栈内元素抛出时栈内抛出的元素的索引和栈外元素i的值配对
②栈内元素抛出后栈外元素i的索引和此时栈顶的元素的值进行配对
出现配对方式的差异的原因时栈内元素的意义不同,
当栈的单调性排列方向与遍历方向一致时:栈内抛出索引-当前元素i值
即栈内元素抛出时栈内抛出的元素的索引和栈外当前元素i的值配对
当栈的单调性排列方向与遍历方向相反时:当前元素i索引-抛出后栈顶元素值
即栈内元素抛出后栈外当前元素i的索引和此时栈顶的元素的值进行配对
(2)while抛出元素循环的不等号
无论正向与反向,
找上一个或下一个更大元素统一用单调递减栈,不等号统一是栈内stk[-1] > (栈外)当前i
找上一个或下一个更小元素统一用单调递增栈,不等号统一是栈内stk[-1] < (栈外)当前i
(3)while抛出元素的是否有等号
如果不存在重复元素,无需考虑此问题;
如果存在重复元素,则需要单独考虑当当前元素i与栈内元素相等时能否配对,
如果相等时可以匹配,则正向遍历无等号,反向有等号
如果相等时不能匹配,则正向遍历有等号,反向无等号,
此外如果是同时寻找上一个和下一个更大或更小,二者合起来写则不需要考虑等号问题,
4.复合使用单调栈
当①下一个更大元素 和 ②上一个更大元素 同时出现时,
或者当③下一个更小元素 和 ④上一个更小元素 同时出现时,
二者可写在一起,无需遍历两次
二、题单-模式识别
1.下一个更大元素:
739. 每日温度(HOT100)
496. 下一个更大元素 I(HOT100)
503. 下一个更大元素 II(HOT100)
2.同时寻找上一个和下一个更大元素:
栈内外可以相等:
42. 接雨水(HOT100)
栈内外不能相当:
654. 最大二叉树:
3.同时寻找上一个和下一个更小元素:
84. 柱状图中最大的矩形(HOT100)
三、题解
1.下一个更大元素
下一个更大元素:
739. 每日温度
正向单调减:
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
n = len(temperatures)
ans = [0] * n
stack = []
for i in range(n):
while stack and temperatures[i] > temperatures[stack[-1]]:
ch = stack.pop()
ans[ch] = i - ch
stack.append(i)
return ans
反向单调减:
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
n = len(temperatures)
ans = [0] * n
stack = []
for i in range(n - 1, -1, -1):
while stack and temperatures[i] >= temperatures[stack[-1]]:
stack.pop()
if stack: ans[i] = stack[-1] - i
stack.append(i)
return ans
496. 下一个更大元素 I
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
n = len(nums1)
ans = [-1] * n
num_map = {v: k for k, v in enumerate(nums1)}
stack = []
for i, num in enumerate(nums2):
while stack and num > nums2[stack[-1]]:
ch = stack.pop()
if nums2[ch] in num_map: ans[num_map[nums2[ch]]] = nums2[i]
stack.append(i)
return ans
503. 下一个更大元素 II
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
n = len(nums)
ans = [-1] * n
stack = []
for i in range(2 * n - 1, -1, -1):
while stack and nums[i % n] >= nums[stack[-1]]:
stack.pop()
if stack: ans[i % n] = nums[stack[-1]]
stack.append(i % n)
return ans
2.同时寻找上一个和下一个更大元素:
栈内外可以相等:
42. 接雨水
(正向)
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height)
stk = []
ans = 0
for i in range(n):
while stk and height[stk[-1]] <= height[i]:
base = stk.pop()
if stk:
h = min(height[stk[-1]], height[i]) - height[base]
w = i - stk[-1] - 1
ans += h * w
stk.append(i)
return ans
栈内外不能相当:
这道题还能看出来,单调栈就是递归找最大值的迭代解法
654. 最大二叉树
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> Optional[TreeNode]:
n = len(nums)
nodes = [TreeNode(nums[i]) for i in range(n)]
stack = []
for i in range(n - 1, -1, -1):
while stack and nums[i] > nums[stack[-1]]:
ch = stack.pop()
nodes[i].right = nodes[ch]
if stack: nodes[stack[-1]].left = nodes[i]
stack.append(i)
return nodes[stack[0]]
3.同时寻找上一个和下一个更小元素:
84. 柱状图中最大的矩形
组合写(反向):
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
left, right = [-1] * n, [n] * n
stack = []
for i in range(n - 1, -1, -1):
while stack and heights[stack[-1]] >= heights[i]:
ch = stack.pop()
left[ch] = i
if stack: right[i] = stack[-1]
stack.append(i)
return max((right[i] - left[i] - 1) * heights[i] for i in range(n))
分开写:
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
left, right = [-1] * n, [n] * n
stack = []
for i in range(n):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack: left[i] = stack[-1]
stack.append(i)
for i in range(n - 1, -1, -1):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack: right[i] = stack[-1]
stack.append(i)
return max((right[i] - left[i] - 1) * heights[i] for i in range(n))