算法和数据结构 1 单调栈
单调栈
扯淡
计算机科学 和主流 数学 的区别在哪里?
计算机科学研究的对象是可数集合,是图灵机。他能够存取数据,每次进行有限步操作。图灵机的一个重要子集是下推自动机,下推自动机相较于有限状态机,区别在于多了一个栈,用来记录状态。
为什么用栈?我们知道,两个栈能模拟一个队列,两个队列也能模拟一个栈,这似乎说明两者有着某种相同的性质,单单从表面看,都不过是一种操作受限的线性表。
事实上,栈会把进入的数据反序,反两次就会变成顺序,因而两个栈模拟一个队列几乎没有任何性能损失;相反来看队列,为了做到先进后出,队列不得不以 O(n)
的复杂度来得到最先进入的元素。
这似乎说明栈有着比队列更为基础的性质。
单调栈和单调队列是进一步限定表内元素序关系的的线性表,跟逆序对一样,我几乎没见过哪本书详细剖析他们的性质。
反过来说,如果有哪本书详细讲到了,那么它名声却小到我都没有听说过,这实在是遗憾。
模板题
这里记录一下简单的思路:
- 倒着遍历数组
a
:- 循环,若
a[i] >= stack.top()
则出栈。 - 上一步停止时,若:
stack.empty()
,那么a[i]
右边没有比他大的数!stack.empty()
,那么stack.top()
就是a[i]
右边第一个比它大的数
stack.push(a[i])
- 循环,若
这样的操作保证了两个事实:
- 栈
stack
是单调的 - 出栈操作停止时,栈顶元素(如果有)一定是第一个比自己大的元素
事实1 比较显然,下面看一下事实2
/// array /////////////////// top <- stack ////
... a, b ... c, d, e, f ...
数组遍历到 b
,出栈操作完成后,栈顶元素 c
必有 c > b
。遍历到 a
时,b
已入栈:
/// array /////////////////// top <- stack ////
... a ... b, c, d, e, f ...
此时若
a<b
,那么b
一定是a
右边第一个比a
大的元素a>=b
,那之前被b
出栈的元素就算不出栈,现在也要被出栈,所以没有影响
应用 1
nums1
是 nums2
的子集,这就是模板题。
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
tmp = {}
stk = []
for i in reversed(nums2):
while(stk and i >= stk[-1]):
stk.pop()
tmp[i] = stk[-1] if stk else -1
stk.append(i)
return [tmp[i] for i in nums1]