栈
本文最后更新于 2023年11月4日 下午
栈
栈这种数据结构,在计算机中有着广泛地运用,比如编程语言中函数的调用、操作系统中从用户态到内核态寄存器的保存、网络消息的处理等都会用到栈。
栈是先进后出(LIFO)顺序。
1 |
|
对应的代码动画演示:
判断字符括号是否合法
【题目】字符串中只有字符’(‘和’)’。合法字符串需要括号可以配对。比如:
输入:”()”
输出:true
解释:(),()(),(())是合法的。)(,()(,(()是非法的。
请你实现一个函数,来判断给定的字符串是否合法。
boolean isValid(String s);
通过四步分析法:
模拟:模拟题目的运行。
规律:尝试总结出题目的一般规律和特点。
匹配:找到符合这些特点的数据结构与算法。
边界:考虑特殊情况。
模拟
首先我们以字符串 s = “()()(())”,进行模拟,如下动图所示:
规律
- 每个左括号’(‘或者右括号’)’都完成配对,才是合法的。
- 配对可以通过消除法来消掉合法的括号,如果最后没有任何字符了,那么就是合法字符串。
- 奇数长度的字符串总是非法的。
匹配
可以用栈来进行消除法的模拟。
边界
找到问题匹配的算法或者数据结构之后,并不是马上写代码,而是要考虑一些边界问题,也就是一些特殊情况:
字符串为空
字符串只有 1 个或者奇数个
字符串是”(((())))”嵌套很多层的是否可以处理
1 |
|
复杂度分析
每个字符只入栈一次,出栈一次,所以时间复杂度为 $O(N)$,而空间复杂度为 $O(N)$,因为最差情况下可能会把整个字符串都入栈。
我们还需要从两个角度进行深度思考:
深度,比如这种解法还可以怎么优化呢?
栈中元素都相同时,实际上没有必要使用栈,只需要记录栈中元素个数。
1 |
|
广度,比如这种解法具有普适性吗?
拓展:给出一个仅包含字符’(‘,’)’,’{‘,’}’,’[‘和’]’,的字符串,判断给出的字符串是否是合法的括号序列
括号必须以正确的顺序关闭,”()”和”()[]{}”都是合法的括号序列,但”(]”和”([)]”不合法。
方法一:
1 |
|
方法二:
1 |
|
大鱼吃小鱼
【题目】在水中有许多鱼,可以认为这些鱼停放在 x 轴上。再给定两个数组 Size,Dir,Size[i] 表示第 i 条鱼的大小,Dir[i] 表示鱼的方向 (0 表示向左游,1 表示向右游)。这两个数组分别表示鱼的大小和游动的方向,并且两个数组的长度相等。鱼的行为符合以下几个条件:
所有的鱼都同时开始游动,每次按照鱼的方向,都游动一个单位距离;
当方向相对时,大鱼会吃掉小鱼;
鱼的大小都不一样。
输入:Size = [4, 2, 5, 3, 1], Dir = [1, 1, 0, 0, 0]
输出:3
请完成以下接口来计算还剩下几条鱼?
int solution(int[] Size, int[] Dir);
模拟
Size = [4, 3, 2, 1, 5], Dir = [0, 1, 0, 0, 0]
timeline
title 大鱼吃小鱼
初始 : [4, 3, 2, 1, 5]
第一次 : [4, 3, 1, 5]
第二次 : [4, 3, 5]
第三次 : [4, 5]
规律
通过模拟,可以发现如下规律:
如果两条鱼相对而游时,那么较小的鱼会被吃掉;
其他情况没有鱼被吃掉。
匹配
我们发现,活下来的鱼的行为就是一个栈。每当有新的鱼要进来的时候,就会与栈顶的鱼进行比较。那么我们匹配到的算法就是栈了。
边界
所有的鱼都朝着一个方向游;
一条鱼吃掉了其他的所有鱼。
代码:
1 |
|
复杂度分析
每只鱼只入栈一次,出栈一次,所以时间复杂度 为 $O(N)$,而空间复杂度为 $O(N)$,因为最差情况下可能把所有的鱼都入栈。
在线题库
单调栈
单调栈就是指栈中的元素必须是按照升序排列的栈,或者是降序排列的栈。对于这两种排序方式的栈,升序排列的栈称为递增栈,降序排列的栈称为递减栈。
找出数组中右边比我小的元素
【题目】一个整数数组 A,找到每个元素:右边第一个比我小的下标位置,没有则用 -1 表示。
输入:[5, 2]
输出:[1, -1]
解释:因为元素 5 的右边离我最近且比我小的位置应该是 A[1],最后一个元素 2 右边没有比 2 小的元素,所以应该输出 -1。
接口:int[] findRightSmall(int[] A);
模拟
1 |
|
规律
小的数要与大的数配对
小的数会消除大的数
匹配
单调栈
图示:
- 首先将 A[0] = 1 的下标 0 入栈。
- 将 A[1] = 2 的下标 1 入栈。满足单调栈。
- 将 A[2] = 4 的下标 2 入栈。满足单调栈。
- 将 A[3] = 9 的下标 3 入栈。满足单调栈。
- 将 A[4] = 4 的下标 4 入栈时,不满足单调性,需要将 A[3] = 9 从栈中弹出去。下标 4 将栈中下标 3 弹出栈,记录 A[3] 右边更小的是 index = 4。
- 将 A[5] = 0 的下标 5 入栈时,不满足单调性,需要将 A[4] = 4 从栈中弹出去。下标 5 将下标 4 弹出栈,记录 A[4] 右边更小的是 index = 5。A[5] = 0 会将栈中的下标 0, 1, 2 都弹出栈,因此也需要记录相应下标右边比其小的下标为 5,再将 A[5] = 0 的下标 5 放入栈中。
- 将 A[6] = 5 的下标 6 放入栈中。满足单调性。
- 此时,再也没有元素要入栈了,那么栈中的元素右边没有比其更小的元素。因此设置为 -1.
代码如下:
1 |
|
复杂度分析
每个元素只入栈一次,出栈一次,所以时间复杂度为$O(N)$,而空间复杂度为$O(N)$,因为最差情况可能会把所有的元素都入栈。
字典序最小的 k 个数的子序列
模拟
规律
匹配
边界
- 假设数组右边有一个最小的数,这个最小的数会把左边的数全部都消掉,然后递增栈里面就只剩下这 1 个数了。这跟题意有点不符合,题意需要的是找到 k = 2 个出来。
- 解决:当剩下的数字个数与栈中的元素刚好能凑够 k 个数时,就不能再消除了,代码如下 :rightLeftNumber + stack.size() == k
- 如果数组是一个升序的数组,那么此时所有的元素都会被压栈。栈中的数目有可能远远超出 k 个。
- 解决:只需要把栈中的多出来的数字弹出来即可。
栈总结
(完)
在线题库