栈————相关算法探讨
- 前言
- 一、有效的括号
- 1.1 思路分析
- 1.2 解法探讨
- 1.2.1 一次 for 循环,左括号入栈
- 1.2.2 一次 for 循环,左括号入栈(使用字典)
- 1.2.3 一次 for 循环,右括号进栈
- 1.2.4 一次 for 循环,右括号进栈(使用字典)
- 二、删除字符串中的所有相邻重复项
- 2.1 思路分析
- 2.2 解法探讨
- 2.2.1 使用栈的思想
- 2.2.2 使用双指针思想
- 三、逆波兰表达式求值
- 3.1 思路分析
- 3.2 解法探讨
- 3.2.1 使用栈
- 3.2.2 简化(使用其他函数)
- 总结
前言
- 前面我们学习了栈的相关知识,下边我们结合 lecode 上有关栈的题目来加深对栈的认识。
一、有效的括号
- 力扣算法题目第 20 题:给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
- 有效字符串需满足:
-
- 左括号必须用相同类型的右括号闭合。
-
- 左括号必须以正确的顺序闭合。
-
- 每个右括号都有一个对应的相同类型的左括号。
-
- 示例 1:
- 输入:s = “()”
- 输出:true
- 示例 2:
- 输入:s = “()[]{}”
- 输出:true
- 示例 3:
- 输入:s = “(]”
- 输出:false
- 示例 4:
- 输入:s = “([])”
- 输出:true
1.1 思路分析
- 首先我们要判断有几种不匹配的情况,在写之前分析好,这样会事半功倍。
- 1、字符串里边左括号多余,所以不匹配。
- 2、字符串里右括号多余了,所以不匹配。
- 3、括号没有多余,但是匹配不上。
- 但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
1.2 解法探讨
1.2.1 一次 for 循环,左括号入栈
- 每当遍历到字符串中的左括号的时候,那就让左括号入栈,
- 当匹配到右括号的时候,
- 我们检查这时候的栈顶元素等不等于这时候的右括号 或者 栈是否为空,如果有一个条件满足,那就返回False
- 如果栈顶元素匹配右括号的话,那就让栈顶元素出栈, 继续循环字符串中的下一个字符
- 当遍历完字符串的时候,栈内如果还有元素的话,那说明没匹配完,返回False
时间复杂度 : O ( n ) O(n) O(n)
空间复杂度 : O ( n ) O(n) O(n)
代码演示:
class Solution:def isValid(self, s: str) -> bool:# 定义一个栈用来存放左括号stack = []# 定义一个tmp用来存放 所有向左的括号tmp = ['(','[','{']# 遍历传进来的字符串sfor item in s:# 如果 遍历到的这个括号 是属于向左的括号if item in tmp:# 将所有的向左的括号入栈stack.append(item)# 开始对 所有向右的 括号进行判断 elif item == ')':# 如果 stack为空 或者 栈顶元素不等于与之匹配的括号if not stack or stack[-1] != '(':# 那就返回 Falsereturn False# 如果 遍历到的右括号 跟栈顶元素 匹配的话, 栈顶元素出栈# stack.pop()else:stack.pop()# 跟上边相同elif item == ']':if not stack or stack[-1] != '[':return False# stack.pop()else:stack.pop()elif item == '}':if not stack or stack[-1] != '{':return False# stack.pop()else:stack.pop()else:return False # 当遍历完字符串的时候,如果栈内还有元素,那说明没匹配完全,返回Falsereturn True if not stack else False
1.2.2 一次 for 循环,左括号入栈(使用字典)
时间复杂度 : O ( n ) O(n) O(n)
空间复杂度 : O ( n ) O(n) O(n)
代码演示:
class Solution:def isValid(self, s: str) -> bool:# 定义一个栈用来存放左括号stack = []# 定义一个字典用来存放右括号right_dict = {')':'(',']':'[','}':'{'}# 开始遍历字符串sfor i in s:# 如果元素是 左括号 也就是字典中的Keyif i in right_dict.values():# 也就是左括号入栈stack.append(i)# 当遍历到的元素 为右括号的时候 判断栈是否为空 或者栈顶元素能不能匹配我们的右括号# elif not stack or stack[-1] != right_dict[i]:# return False# # 走到这 说明匹配上了# else:# stack.pop()# 当栈为空的时候,也就是字符串中没有 字符串进栈,或者当出栈以后栈为空elif not stack:return False# 当栈顶元素跟我们的右括号匹配的话,那栈顶元素就出栈elif stack[-1] == right_dict[i]:stack.pop()else:return False# 最后如果栈内还有元素的话,那就说明 没匹配完全,返回Falsereturn True if not stack else False
1.2.3 一次 for 循环,右括号进栈
时间复杂度 : O ( n ) O(n) O(n)
空间复杂度 : O ( n ) O(n) O(n)
代码演示:
class Solution:def isValid(self, s: str) -> bool:# 定义一个栈用来存放右括号stack = []for item in s:# 当 遍历到左括号的时候那就右括号进栈if item == '(':stack.append(')')elif item == '[':stack.append(']')elif item == '{':stack.append('}')# 若遍历到右括号的时候,我们只需要比较栈顶元素跟遍历到的右括号相等不相等elif not stack or stack[-1] != item:return False# 走到这说明,说明栈顶元素跟遍历到的元素匹配上了,所以栈顶元素要出栈else:stack.pop()return True if not stack else False
1.2.4 一次 for 循环,右括号进栈(使用字典)
时间复杂度 : O ( n ) O(n) O(n)
空间复杂度 : O ( n ) O(n) O(n)
代码演示:
class Solution:def isValid(self, s: str) -> bool:stack = []mapping = {'(': ')','[': ']','{': '}'}for item in s:if item in mapping.keys():stack.append(mapping[item])elif not stack or stack[-1] != item: return Falseelse: stack.pop()return True if not stack else False
二、删除字符串中的所有相邻重复项
- 力扣算法题目第 20 题:
- 给出由小写字母组成的字符串 s,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
- 在 s 上反复执行重复项删除操作,直到无法继续删除。
- 在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
- 示例 1 :
- 输入:“abbaca”
- 输出:“ca”
- 解释:例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。
- 提示:
- 1 <= s.length <= 1 0 5 10^5 105
- s 仅由小写英文字母组成
2.1 思路分析
- 我们可以使用栈的思想,遍历到一个元素就判断跟栈顶元素是否相等,如果相等的话,说明字符串中这两个字符重复,这时候让栈顶元素出栈即可,如果,栈顶元素跟遍历到的元素不同的话,这个元素入栈。
- 当然我们还可以使用双指针:刚开始两个指针都指向第一个元素,fast指针开始走,并且比较该元素跟slow指针所指的元素是否相等,如果相等,删除这两个元素,并且slow指针往回退一个,fast指针继续往后走。
2.2 解法探讨
2.2.1 使用栈的思想
代码演示:
class Solution:def removeDuplicates(self, s: str) -> str:# 定义一个空栈,stack = []# 开始遍历字符串for i in s:# 如果栈不为空,并且栈顶元素等于遍历到的元素,那么栈顶元素出栈if stack and stack[-1] == i:stack.pop()# 走到这,说明栈为空或者栈顶元素不等于遍历到的元素,那就将该元素入栈else:stack.append(i)# 我们得到的 stack 相当于是列表,所以要将其转成字符串return ''.join(stack)
2.2.2 使用双指针思想
代码演示:
class Solution:def removeDuplicates(self, s: str) -> str:res = list(s)slow = fast = 0length = len(res)while fast < length:# 如果一样直接换,不一样会把后面的填在slow的位置res[slow] = res[fast]# 如果发现和前一个一样,就退一格指针if slow > 0 and res[slow] == res[slow - 1]:slow -= 1else:slow += 1fast += 1return ''.join(res[0: slow])
三、逆波兰表达式求值
- 力扣算法题目第 150 题:给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
- 请你计算该表达式。返回一个表示表达式值的整数。
- 注意:
-
- 有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
-
- 每个操作数(运算对象)都可以是一个整数或者另一个表达式。。
-
- 两个整数之间的除法总是 向零截断 。
-
- 表达式中不含除零运算。
-
- 输入是一个根据逆波兰表示法表示的算术表达式。
-
- 答案及所有中间计算结果可以用 32 位 整数表示。
-
- 示例 1:
- 输入:tokens = [“2”,“1”,“+”,“3”,“*”]
- 输出:9
- 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
- 示例 2:
- 输入:tokens = [“4”,“13”,“5”,“/”,“+”]
- 输出:6
- 解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
- 示例 3:
- 输入:tokens = [“10”,“6”,“9”,“3”,“+”,“-11”,““,”/“,””,“17”,“+”,“5”,“+”]
- 输出:22
- 解释:该算式转化为常见的中缀算术表达式为:
((10 * ( 6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * ( 6 / (12 * -11))) + 17) + 5
= ((10 * ( 6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
- 提示:
- 1 <= tokens.length <= 1 0 4 10^4 104
- tokens[i] 是一个算符(“+”、“-”、“*” 或 “/”),或是在范围 [-200, 200] 内的一个整数
3.1 思路分析
- 逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
- 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
- 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
- 逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
- 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
3.2 解法探讨
3.2.1 使用栈
时间复杂度 : O ( n ) O(n) O(n)
空间复杂度 : O ( n ) O(n) O(n)
代码演示:
class Solution:def evalRPN(self, tokens: List[str]) -> int:# 定义一个栈stack = []# 定义一个列表放 加减乘除 符号char_list = ['+', '-', '*', '/']# 开始遍历 字符串中的字符for token in tokens:# 如果是数字,那就入栈if token not in char_list:stack.append(token)# 如果运算符,那就从栈中取出来 两个数进行运算else:if token == '+':# 栈顶元素为 运算符后边的数,因为op2 = int(stack.pop())op1 = int(stack.pop())res = op1 + op2stack.append(res)elif token == '-':# 因为取出来是字符,字符不能运算,必须转成intop2 = int(stack.pop())op1 = int(stack.pop())res = op1 - op2stack.append(res)elif token == '*':op2 = int(stack.pop())op1 = int(stack.pop())res = op1 * op2stack.append(res)# 到这就是除法else:op2 = int(stack.pop())op1 = int(stack.pop())# 但是除法要向零截断,所以有这步res = op1 / op2 if op1*op2 > 0 else -(abs(op1)//abs(op2))stack.append(res)# 返回栈顶元素return int(stack.pop())
3.2.2 简化(使用其他函数)
时间复杂度 : O ( n ) O(n) O(n)
空间复杂度 : O ( n ) O(n) O(n)
代码演示:
from operator import add, sub, muldef div(x, y):# 使用整数除法的向零取整方式return int(x / y) if x * y > 0 else -(abs(x) // abs(y))class Solution(object):op_map = {'+': add, '-': sub, '*': mul, '/': div}def evalRPN(self, tokens: List[str]) -> int:stack = []for token in tokens:if token not in {'+', '-', '*', '/'}:stack.append(int(token))else:op2 = stack.pop()op1 = stack.pop()stack.append(self.op_map[token](op1, op2)) # 第一个出来的在运算符后面return stack.pop()
总结
- 以上就是用到了栈结构的相关题目。