【leetcode】栈、队列相关题目思路总结(更新中)
简单题
面试题03.02 栈的最小值
剑指offer30 包含min函数的栈
155. 最小栈
https://leetcode-cn.com/problems/min-stack/
https://leetcode-cn.com/problems/min-stack-lcci/
https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/
题目要求:
自定义栈,在满足pop和push之外,再提供min方法,可以获取栈中最小的元素。
解题思路:
额外维护一个最小值栈,记录栈中的最小值。
因为栈的最小值就在栈顶,所以每次新加入元素时,只需要与栈顶比较,即可知道新的最小值是多少。
如果新元素更小,则新元素复制一份入最小值栈,如果不是,则将栈顶再复制一份入栈。
复杂度分析:
push、pop、min都是O(1)的时间复杂度。
空间复杂度为O(n)。
剑指offer09 用两个栈实现队列
面试题 03.04. 化栈为队
232. 用栈实现队列
https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/
https://leetcode-cn.com/problems/implement-queue-using-stacks-lcci/
https://leetcode-cn.com/problems/implement-queue-using-stacks/
题目要求:
用两个栈实现一个队列。
解题思路:
用一个栈in_stack专门进行push,用一个栈out_stack来pop。
如果out_stack为空,则将in_stack的倒入out_stack。
queue: 1(front), 2, 3, 4, 5(back)
in_stack: 4, 5(top), out_stack: 3, 2, 1(top)
复杂度分析:
每个元素最多被插入和弹出in_stack out_stack各一次,所以时间复杂度O(1)。
空间复杂度为O(n)。
225 用队列实现栈
https://leetcode-cn.com/problems/implement-stack-using-queues/
题目要求:
用两个队列实现栈。
解题思路:
pop元素直接可以从队列顶删去,push元素需要一个临时栈,先将新元素进队,然后再将其他元素进队。之后再全部倒回原队列,或者互换两个队列。
用一个队列也是可以的,就是将n-1个元素先出队再入队。
queue: 5 4 3 2 1
queue_temp: 6
stack: 1 2 3 4 5 + 6
复杂度分析:
入栈操作 O(n),其余操作都是 O(1)。
20 有效的括号
题目要求:
判断大括号、中括号、小括号的顺序是否合法。
解题思路:
用栈来解决即可。对于配对问题,可以直接if-else,也可以用unordered_map。
复杂度分析:
时间复杂度:O(n),和序列等长。
空间复杂度:O(n+∣Σ∣) ,序列+字典长。
844 比较含退格的字符串
https://leetcode-cn.com/problems/backspace-string-compare/
题目要求:
字符串中包括退格符#,判断两个字符串是否相等。
解题思路:
方法一,用栈,遇到普通字符入栈,遇到#则出栈。时间和空间复杂度均为O(M+N),M和N是两个字符串的长度。
方法二,双指针,定义两个指针从两个字符串的末尾开始向前比较。时间复杂度为O(M+N),空间复杂度为O(1)。
1047 删除字符串中的所有相邻重复项
https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/
题目要求:
连连看,不断从字符串中删除成对相同的元素。
解题思路:
方法一,直接用字符串的替换方法,将aa,bb,...,zz全部替换为空字符串。因为需要反复多次,所以时间复杂度O(n^2), 空间复杂度O(n)。
方法二,用栈。时间复杂度和空间复杂度均为O(n)。
346. 数据流中的移动平均值
https://leetcode-cn.com/problems/moving-average-from-data-stream/
题目要求:
计算一个移动窗口内的数据的平均值,窗口大小固定。
解题思路:
方法一,每次将新元素入队列后,遍历计算窗口内的数据的平均值,时间复杂度为O(n),空间复杂度为O(M), M是序列长。
方法二,维持队列的大小,每次入队列后,如果超过窗口大小,则出队列一个,并记录每次的平均值,通过加减计算得到新的平均值。时间复杂度O(1), 空间复杂度O(N)。
方法三,采用基于数组的循环队列,时间空间复杂度同上,优点是不用显式地执行出队操作。
剑指 Offer 59 - I. 滑动窗口的最大值 (好题,思路经典)
https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/
题目要求:
窗口大小固定,求一个移动窗口里的最大值。
解题思路:
采用deque实现单调队列,维护一个非严格递减的队列。要很好地理解这一思想,需要用高度遮挡做类比。
这个队列的队首元素是区间里的最大的元素。当数据流出队的值和单调队列的队首值相等,则单调队列执行出队,否则,不用操作。
当数据流入队的值比单调队列队尾的值大时,丢弃该队尾值,如果还有小的,则继续丢弃。
时间复杂度O(n), 空间复杂度O(k), n为数据流队列长度,k为窗口大小。
1441. 用栈操作构建数组
https://leetcode-cn.com/problems/build-an-array-with-stack-operations/
题目要求:
从1到n按顺序遍历数,然后入栈或者出栈,得到操作序列。
解题思路:
如果数组中有的数,则是执行了一次入栈,如果没有,则执行了入栈+出栈。
933. 最近的请求次数
题目要求:
不断发送ping请求,返回最近3000毫秒的请求次数。
解题思路:
每次入队的时候,循环判断队首是否在3000毫秒以内,如果不是则出队,最后返回队长。
682. 棒球比赛
https://leetcode-cn.com/problems/baseball-game/
题目要求:
一个特殊的积分规则。
解题思路:
使用栈实现,其中上次比赛和上上次比赛的积分求和,可以先出栈一次,再入栈一次。
时间空间复杂度都是O(N)。
496. 下一个更大元素 I
https://leetcode-cn.com/problems/next-greater-element-i/
题目要求:
求数组后面比数组当前值更大的下一个值。
解题思路:
这道题和滑动窗口最大值类似,也是高度遮挡的概念。
维护一个非严格递减栈,从右向左遍历。
或者一个非严格递增栈,从左向右遍历。
1021. 删除最外层的括号
https://leetcode-cn.com/problems/remove-outermost-parentheses/
题目要求:
找原语,并去掉原语外侧的括号,再合并。
解题思路:
判断括号的层级深度,如果是1级的括号,则忽略,2级及更深层的则直接复制给结果,这道题没有必要用栈。
1544. 整理字符串
https://leetcode-cn.com/problems/make-the-string-great/
题目要求:
将成对的大小写字母删掉。
解题思路:
不需要用栈,直接扫描字符串,如果有成对的,则忽略,将后面的往前复制(移动)。
可以实现就地算法。
1598. 文件夹操作日志搜集器
https://leetcode-cn.com/problems/crawler-log-folder/
题目要求:
给一些操作序列,判断最终返回主目录的步数。
解题思路:
可以使用栈,也可以不使用而直接用if-else计数。
中等题
402. 移掉K位数字 (好题,考验细节)
https://leetcode-cn.com/problems/remove-k-digits/
题目要求:
将一个数字中的k位删掉,最后得到的数要最小。
解题思路:
最终得到的数,应该是数字高位尽可能小,因此:
方法一,从左至右,设定窗口大小k+1,然后挑选最小的数字保留,该数左边的删去(最多删k个)。
方法二,使用非严格递增的单调栈,遍历,如果后面遇到的数比前边的小,则逐个删掉前边的数,保持序列递增性,用栈实现。
要处理的特殊情况:
k个数字要保证删够。
如果最终的数字序列存在前导零,我们要删去前导零。
如果最终数字序列为空,我们应该返回 0。
739. 每日温度
https://leetcode-cn.com/problems/daily-temperatures/
题目要求:
给一个温度序列,返回到下一个温度更高的时间的距离。
解题思路:
这道题和下一个更大元素题类似,不过是增加了距离计算。
多设置一个stack同步下标,即可得到距离。
另外一种方法是,利用输入vector按index取值,以及用输出vector元素自增,只用一个保存下标的stack即可。
1190. 反转每对括号间的子串(好题,思路清奇)
https://leetcode-cn.com/problems/reverse-substrings-between-each-pair-of-parentheses/
题目要求:
将一个字符串里括号中的内容逐级反转。
解题思路:
方法一,可以用栈判断括号的层级,然后写一个函数专门反转字符串,时间复杂度为O(n^2)。
方法二,一个很神奇的方法(黑洞法),先遍历一遍确定括号位置存到map里,然后开始遍历,遇到括号则跳到对应的括号处,并更改方向,前进一步。边界条件是,最终会跳出序列,只要判断位置合法。
方法二的时间复杂度为O(n),思路很不错,很难想到。
456. 132模式
https://leetcode-cn.com/problems/132-pattern/
题目要求:
从序列中找到符合132模式的数字,即3元组,左边<右边<中间。
解题思路:
首先从左到右遍历一遍得到最小值栈,这样下一步从右往左遍历的时候,可以知道左边有更小的值。
然后从右往左遍历,将当前值按条件依次入栈,同时最小值栈依次出栈。
条件:如果当前值小于栈顶则入栈,等于则跳过。大于栈顶则找到了比右边大的数。
不过右边的数不一定是更大的,所以在不大于当前值的情况下,逐个出栈,保证取到较大的数(当前值应当也在遍历时插入其中,后续会使用到)。
此时再判断如果这两个数比最小值栈栈顶大,则132三元组就找到了。
856. 括号的分数
https://leetcode-cn.com/problems/score-of-parentheses/
题目要求:
给定一个平衡括号字符串 S,按下述规则计算该字符串的分数:
() 得 1 分。
AB 得 A + B 分,其中 A 和 B 是平衡括号字符串。
(A) 得 2 * A 分,其中 A 是平衡括号字符串。
解题思路:
方法一,先遍历一遍,找到括号对,索引存到map里。然后将这一级的括号结果相加,下一级的则递归。时间空间复杂度均为O(n)。
方法二,维护一个栈,动态存储结果,遇到左括号则将0入栈,遇到右括号,则结果加到栈顶,同一级的直接相加,返回上级则乘2加到上级,最后得到总的结果,时间空间复杂度均为O(n)。
方法三,数学本质,找到层级最高的括号,则这个括号的值为2^n,最后把这些最高层级的括号相加既可,时间复杂度O(n),空间O(1)。
1209. 删除字符串中的所有相邻重复项 II
https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string-ii/
题目要求:
给你一个字符串 s,「k 倍重复项删除操作」将会从 s 中选择 k 个相邻且相等的字母,并删除它们,使被删去的字符串的左侧和右侧连在一起。
你需要对 s 重复进行无限次这样的删除操作,直到无法继续为止。
在执行完所有删除操作后,返回最终得到的字符串。
解题思路:
这道题方法较多,基本上都是要用到栈,空间时间复杂度最优为O(n)。
类似快慢双指针法,用两个双端队列,一个存字符,一个存计数,扫描字符串,如果字符相同,则累加计数,计数达到3,则删除。最后从队列前端倒出来,组成新字符串。
394. 字符串解码
https://leetcode-cn.com/problems/decode-string/
题目要求:
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
解题思路:
因为会有嵌套括号存在的情况,所以需要从最深层逐层向上处理,因此要保存好各层的变量。
使用一个栈保存该级待重复次数,使用一个栈保存该级需要重复的字母数,使用一个双端队列作为输入输出的数据流。
需要考虑的细节:
重复次数数字可能大于个位数,所以需要对数字进行拼接。
深层的重复后,需要更新上一级的字母数。
因为可能一开始只有字母,因此这个也要算一级(重复次数为1)。
946. 验证栈序列
https://leetcode-cn.com/problems/validate-stack-sequences/
题目要求:
判断list2是不是list1的合法出栈序列。
解题思路:
贪心法,每次先从list1中取一个数入栈,然后循环判断栈顶和list2头部的元素是否相等,相等则出栈。最后判断栈是否已清空,未清空则为false,已清空则为true。
71. 简化路径
https://leetcode-cn.com/problems/simplify-path/
题目要求:
给一个linux的路径,然后简化一下,其中有.以及..还有多个/。
解题思路:
压栈既可,注意对于几个特殊符号的处理逻辑的严密性,简单题。
144. 二叉树的前序遍历(后续需要学习Morris方法)
https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
题目要求:
前序遍历。
解题思路:
用栈实现,判断是否为空,非空则先将父节点入栈。
然后循环执行,栈顶出栈,结果保存,将右子树入栈,再将左子树入栈,直到栈内为空。这种方法的时空复杂度均为O(n)。
此外还有一种Morris方法,充分利用空闲指针,空间复杂度为O(1)。值得学习,后面几道题都可以用。
94. 二叉树的中序遍历
https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
题目要求:
中序遍历。
解题思路:
用栈实现,判断是否为空,非空则将父节点入栈。
然后循环执行,栈顶出栈,判断如果没有左右子树,则将值保存,否则,将右子树入栈,在将父节点减掉子树后入栈,将左子树入栈。
145. 二叉树的后序遍历
https://leetcode-cn.com/problems/binary-tree-postorder-traversal/
题目要求:
后序遍历。
解题思路:
与上面两题类似,不再赘述。
582. 杀死进程
https://leetcode-cn.com/problems/kill-process/
题目要求:
进程的父子关系类似于树,一个父进程会有几个子进程,求一个进程被杀死后,哪些进程一起死掉了。
解题思路:
方法一,可以直接构造树,然后遍历要杀死的子树。时间空间复杂度为O(n)。
方法二,用哈希表保存父子关系,然后用BFS或者DFS进行遍历。
面试题 17.09. 第 k 个数
https://leetcode-cn.com/problems/get-kth-magic-number-lcci/
题目要求:
一个数因式分解只能包括3,5,7这三种,1也算。序列1,3,5,7,9,15,求第k个。
解题思路:
这道题的解题思路非常巧妙。
先弄个vector存结果,然后设置三个指针,表示3,5,7*的vector里的哪个位置的元素。
找到值最小的那个,并将对应相等的指针(可能不止一个)向后移动。
622. 设计循环队列
https://leetcode-cn.com/problems/design-circular-queue/
题目要求:
基于定长数组实现一个循环队列。
解题思路:
主要采用两个指针实现,一个头指针,一个尾指针。区间为左闭右开。各种条件判断要注意不要出错。
641. 设计循环双端队列
https://leetcode-cn.com/problems/design-circular-deque/
题目要求:
基于定长数组实现一个双端队列。
解题思路:
和上题类似,也是用两个指针,不同点就是插入和删除多了一个方向。
173. 二叉搜索树迭代器
https://leetcode-cn.com/problems/binary-search-tree-iterator/
题目要求:
根据二叉搜索树构建,可以返回next()和hasNext(),要求O(1)时间复杂度,O(k)空间。
解题思路:
因为二叉搜索树是可以通过中序遍历获得最小值的,所以在初始化方法中先做树的中序遍历,转存到queue中。
921. 使括号有效的最少添加(TODO:理解一下第二种方法)
https://leetcode-cn.com/problems/minimum-add-to-make-parentheses-valid/
题目要求:
使括号序列有效,最少需要添加的括号数目。
解题思路:
用栈存,括号成对的删去,不成对的就是最后的结果。
此外,还有一个方法,计算前缀子数组的平衡度,根据正负加上相应左右括号。
1381. 设计一个支持增量操作的栈
https://leetcode-cn.com/problems/design-a-stack-with-increment-operation/
题目要求:
设计固定长度的栈,可以支持一定限度的push,以及对栈底k个元素值的增加。
解题思路:
直接用vector来模拟。简单题。
比较骚气的一个方法是,可以把要增加的先存起来,在出栈的时候计算,这样整个的时间复杂度就变成O(1)了。