一、单调栈
简介:
单调栈(Monotone Stack):一种特殊的栈。在栈的先进后出
规则基础上,要求从 栈顶 到 栈底 的元素是单调递增(或者单调递减)
。其中满足从栈顶到栈底的元素是单调递增的栈,叫做单调递增栈
。满足从栈顶到栈底的元素是单调递减的栈,叫做单调递减栈
。
1.1单调递增栈
单调递增栈:只有比栈顶元素小的元素才能直接进栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈。
单调递增栈出入栈过程:
- 假设当前进栈元素为 x,如果栈顶元素大于 x,则直接入栈。
- 否则从栈顶开始遍历栈中元素,把小于 x 或者等于 x 的元素弹出栈,直到遇到一个大于 x 的元素为止,然后再把 x 压入栈中。
1.2单调递减栈
**单调递减栈:**只有比栈顶元素大的元素才能直接进栈,否则需要先将栈中比当前元素大的元素出栈,再将当前元素入栈。
单调递减栈出入栈过程:
- 假设当前进栈元素为 x,如果栈顶元素小于 x,则直接入栈。
- 否则从栈顶开始遍历栈中元素,把大于 x 或者等于 x 的元素弹出栈,直到遇到一个小于 x 的元素为止,然后再把 x 压入栈中。
1.3单调栈适用场景
单调栈可以在时间复杂度为 的情况下,求解出某个元素左边或者右边第一个比它大或者小的元素。
所以单调栈一般用于解决一下几种问题:
- 寻找左侧第一个比当前元素大的元素
- 寻找左侧第一个比当前元素小的元素
- 寻找右侧第一个比当前元素大的元素
- 寻找右侧第一个比当前元素小的元素
-
寻找左侧第一个比当前元素大的元素
从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。如果插入时的栈为空,则说明左侧不存在比当前元素大的元素。 -
寻找左侧第一个比当前元素小的元素
从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素左侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。如果插入时的栈为空,则说明左侧不存在比当前元素小的元素。 -
寻找右侧第一个比当前元素大的元素
- 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素右侧第一个比它大的元素就是将其「弹出单调递增栈」时即将插入的元素。如果该元素没有被弹出栈,则说明右侧不存在比当前元素大的元素。
- 从右到左遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素右侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。如果插入时的栈为空,则说明右侧不存在比当前元素大的元素。
-
寻找右侧第一个比当前元素小的元素
- 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素右侧第一个比它小的元素就是将其「弹出单调递减栈」时即将插入的元素。如果该元素没有被弹出栈,则说明右侧不存在比当前元素小的元素。
- 从右到左遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素右侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。如果插入时的栈为空,则说明右侧不存在比当前元素小的元素。
上边的分类解法有点绕口,可以简单记为以下条规则:
无论哪种题型,都建议从左到右遍历元素。
查找 比当前元素大的元素
就用 单调递增栈,查找 比当前元素小的元素
就用 单调递减栈。
从 左侧
查找就看 插入栈
时的栈顶元素,从 右侧
查找就看 弹出栈
时即将插入的元素。
1.4 代码模板
单调递增栈:
def monotoneIncreasingStack(nums):
stack = []
for num in nums:
while stack and num >= stack[-1]:
stack.pop()
stack.append(num)
单调递减栈:
def monotoneDecreasingStack(nums):
stack = []
for num in nums:
while stack and num <= stack[-1]:
stack.pop()
stack.append(num)
二、496.下一个更大的元素 I
题目描述:
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。
示例:
输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
-4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
-1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
-2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
分析:
这个题对应第三种情况,寻找右侧第一个比当前元素大的元素,因此,用单调栈进行解决即可。
代码:
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
reslut = []
for num1 in nums1:
stack = []
find = False
for num2 in nums2:
while stack and num2 >= stack[-1]:
if stack[-1] == num1:
reslut.append(num2)
find = True
stack.pop()
stack.append(num2)
if not find:
reslut.append(-1)
return reslut
运行结果:
这个用时太离谱了。发现我没用到单调栈的好处,反倒写成了暴力解法,时间复杂度
O
(
N
2
)
O(N^2)
O(N2)。
参考作者的答案,先构造单调栈后,将每一个元素的下一个最大元素都储存在一个哈希表中,在遍历第一个数组寻找答案。
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
res = []
stack = []
num_map = dict()
for num in nums2:
while stack and num > stack[-1]:
num_map[stack[-1]] = num
stack.pop()
stack.append(num)
for num in nums1:
res.append(num_map.get(num, -1))
return res
这样就快多了。算法时间复杂度为
O
(
N
)
O(N)
O(N)。
三、739.每日温度
题目描述:
请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
分析:
可以直接将数组的索引作为元素,而温度作为判定条件来构建单调递增栈,每次进行出栈操作时,对现有栈内的元素都计算索引差值,即为等待升温的天数。
代码:
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
n = len(temperatures)
stack = []
result = [0 for _ in range(n)]
for i in range(n):
while stack and temperatures[i] > temperatures[stack[-1]]:
index = stack.pop()
result[index] = i-index
stack.append(i)
return result
运行结果:
内存耗费了比较多,因此多存了个辅助数组。