单调栈介绍
单调栈是一种特殊的栈数据结构,用于解决一类问题,其中元素需要按照某种顺序或规则进行处理。它的特点在于栈内元素保持严格的单调性,可以是递增或递减。
为什么需要单调栈?
在解决一些关于区间、连续子数组或者寻找下一个更大(或更小)元素的问题时,单调栈能够提供高效的解决方案。它通过维护一个递增(或递减)的栈,可以迅速找到当前元素在特定条件下的位置或者影响范围。
单调栈的基本思想
- 对于递增栈:在入栈操作时,不断弹出栈顶元素,直到栈为空或者遇到比当前元素小的元素。这样能够确保栈底到栈顶元素的单调递增。
- 对于递减栈:类似地,在入栈操作时,不断弹出栈顶元素,直到栈为空或者遇到比当前元素大的元素。这样能够确保栈底到栈顶的元素的单调递减。
单调栈的应用
单调栈常常用于解决以下问题:
- 寻找下一个更大元素或下一个更小元素。
在接下来的博客中,我们将深入讨论如何使用它解决力扣1944. 队列中可以看到的人数。
题目描述
有 n 个人排成一个队列,从左到右编号为 0 到 n - 1。给定一个整数数组 heights
,每个整数表示第 i 个人的高度。一个人能够看到他右边另一个人的条件是这两人之间的所有人都比他们两人矮。更正式的说,第 i 个人能够看到第 j 个人的条件是 i < j 且 min(heights[i], heights[j]) > max(heights[i+1], heights[i+2], ..., heights[j-1])
。请返回一个长度为 n 的数组 answer
,其中 answer[i]
是第 i 个人在他右侧队列中能看到的人数。
示例
示例 1:
输入:
heights = [10, 6, 8, 5, 11, 9]
输出:
[3, 1, 2, 1, 1, 0]
解释:
- 第 0 个人能看到编号为 1,2 和 4 的人。
- 第 1 个人能看到编号为 2 的人。
- 第 2 个人能看到编号为 3 和 4 的人。
- 第 3 个人能看到编号为 4 的人。
- 第 4 个人能看到编号为 5 的人。
- 第 5 个人谁也看不到,因为他右边没有人。
示例 2:
输入:
heights = [5, 1, 2, 3, 10]
输出:
[4, 1, 1, 1, 0]
思路解析
这是一道单调栈的模板题。我们可维护一个栈底到栈顶严格递减的栈,从height
的右侧开始迭代,一旦发现当前迭代的人的高度和栈顶的人的高度相等或者更高,说明当前迭代的人会遮挡栈顶的人,此时可以直接弹出栈顶元素,并记录当前迭代的人能看到的右侧的人数加一。
代码迭代部分解释:
- 遍历队列从右到左,对每个人进行判断。
- 如果当前迭代的人的高度破坏了严格递减的单调性,说明当前高度的人遮挡了右侧比他矮的人,因此我们可以弹出栈顶元素,同时记录当前迭代的人能看到的右侧的人数加一。
- 如果栈中还有元素,说明有一个更高的人在右侧还可以被看到,也需要加一。
- 将当前迭代的人的高度入栈。
代码
class Solution:
def canSeePersonsCount(self, heights: List[int]) -> List[int]:
# 除了最右侧的人,每个人至少能看到自己右侧第一个人。
ans = [0] * len(heights)
stk = deque()
for idx in range(len(heights) - 1, -1, -1):
h = heights[idx]
# 每当当前高度破坏了严格递减的单调性
# 说明当前高度的人遮挡了右侧比他矮的人
# 既然被遮挡了,说明接下来的迭代的人都不会看到这些人。
while stk and stk[-1] <= h:
ans[idx] += 1
stk.pop()
# 如果栈中元素还有剩余,说明有一个更高的人在右侧还可以看到。
if stk:
ans[idx] += 1
stk.append(h)
return ans
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),其中 n 为队列中的人数。
- 空间复杂度: O ( n ) O(n) O(n),使用了一个栈来辅助计算。
总结
本题使用单调栈的思想,从右到左遍历队列,维护一个递减的栈,记录每个人能看到的右侧人数。通过栈的弹出和入栈操作,避免了重复比较,提高了算法的效率。