20. 有效的括号
题解1:
取巧的解法
class Solution:
def isValid(self, s: str) -> bool:
while '{}' in s or '()' in s or '[]' in s:
s = s.replace('{}', '')
s = s.replace('[]', '')
s = s.replace('()', '')
return s == ''
题解2:
使用栈
step1:初始化栈 S。
step2:依次处理表达式的每个括号。
step3:如果遇到开括号,我们只需将其推到栈上即可。这意味着我们将稍后处理它,让我们简单地转到前面的 子表达式。
step4:如果我们遇到一个闭括号,那么我们检查栈顶的元素。如果栈顶的元素是一个 相同类型的 左括号,那么我们将它从栈中弹出并继续处理。否则,这意味着表达式无效。
step5:如果到最后我们剩下的栈中仍然有元素,那么这意味着表达式无效。
class Solution:
def isValid(self, s: str) -> bool:
mapping = {")":"(", "}":"{", "]":"["}
stack = []
for char in s:
if char in mapping:
top_element = stack.pop() if stack else "!"
if top_element != mapping[char]:
return False
else:
stack.append(char)
return len(stack) == 0
22. 括号生成
方法一:深度优先遍历
我们以 n = 2 为例,画树形结构图。方法是 “做减法”。
画图以后,可以分析出的结论:
当前左右括号都有大于 00 个可以使用的时候,才产生分支;
产生左分支的时候,只看当前是否还有左括号可以使用;
产生右分支的时候,还受到左分支的限制,右边剩余可以使用的括号数量一定得在严格大于左边剩余的数量的时候,才可以产生分支;
在左边和右边剩余的括号数都等于 00 的时候结算。
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
cur_str = ''
def dfs(cur_str, left, right):
"""
:param cur_str: 从根结点到叶子结点的路径字符串
:param left: 左括号还可以使用的个数
:param right: 右括号还可以使用的个数
:return:
"""
if left == 0 and right == 0:
res.append(cur_str)
return
if right < left:
return
if left > 0:
dfs(cur_str + '(', left - 1, right)
if right > 0:
dfs(cur_str + ')', left, right - 1)
dfs(cur_str, n, n)
return res
方法二:动态规划
第 1 步:定义状态 dp[i]:使用 i 对括号能够生成的组合。
注意:每一个状态都是列表的形式。
第 2 步:状态转移方程:
i 对括号的一个组合,在 i - 1 对括号的基础上得到,这是思考 “状态转移方程” 的基础;
i 对括号的一个组合,一定以左括号 "(" 开始,不一定以 ")" 结尾。为此,我们可以枚举新的右括号 ")" 可能所处的位置,得到所有的组合;
枚举的方式就是枚举左括号 "(" 和右括号 ")" 中间可能的合法的括号对数,而剩下的合法的括号对数在与第一个左括号 "(" 配对的右括号 ")" 的后面,这就用到了以前的状态。
状态转移方程是:
dp[i] = "(" + dp[可能的括号对数] + ")" + dp[剩下的括号对数]
“可能的括号对数” 与 “剩下的括号对数” 之和得为 i - 1(感谢 @xuyik 朋友纠正了我的错误),故 “可能的括号对数” j 可以从 0 开始,最多不能超过 i, 即 i - 1;
“剩下的括号对数” + j = i - 1,故 “剩下的括号对数” = i - j - 1。
整理得:
dp[i] = "(" + dp[j] + ")" + dp[i- j - 1] , j = 0, 1, ..., i - 1
第 3 步: 思考初始状态和输出:
初始状态:因为我们需要 0 对括号这种状态,因此状态数组 dp 从 0 开始,0 个括号当然就是 [""]。
输出:dp[n] 。
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
if n == 0:
return []
dp = [None for _ in range(n + 1)]
dp[0] = [""]
for i in range(1, n + 1):
cur = []
for j in range(i):
left = dp[j]
right = dp[i - j - 1]
for s1 in left:
for s2 in right:
cur.append("(" + s1 + ")" + s2)
dp[i] = cur
return dp[n]
32. 最长有效括号
方法1:栈
具体做法是我们始终保持栈底元素为当前已经遍历过的元素中「最后一个没有被匹配的右括号的下标」,这样的做法主要是考虑了边界条件的处理,栈里其他元素维护左括号的下标:
对于遇到的每个 ‘(’ ,我们将它的下标放入栈中
对于遇到的每个 ‘)’ ,我们先弹出栈顶元素表示匹配了当前右括号:
如果栈为空,说明当前的右括号为没有被匹配的右括号,我们将其下标放入栈中来更新我们之前提到的「最后一个没有被匹配的右括号的下标」
如果栈不为空,当前右括号的下标减去栈顶元素即为「以该右括号为结尾的最长有效括号的长度」
我们从前往后遍历字符串并更新答案即可。
需要注意的是,如果一开始栈为空,第一个字符为左括号的时候我们会将其放入栈中,这样就不满足提及的「最后一个没有被匹配的右括号的下标」,为了保持统一,我们在一开始的时候往栈中放入一个值为 −1 的元素。
class Solution:
def longestValidParentheses(self, s: str) -> int:
if not s:
return 0
res = 0
stack = [-1]
for i in range(len(s)):
if s[i] == "(":
stack.append(i)
else:
stack.pop()
if not stack:
stack.append(i)
else:
res = max(res,i - stack[-1])
return res
方法2:动态规划
class Solution:
def longestValidParentheses(self, s: str) -> int:
n = len(s)
if n == 0: return 0
dp = [0] * n
res = 0
for i in range(n):
if i>0 and s[i] == ")":
if s[i - 1] == "(":
dp[i] = dp[i - 2] + 2
elif s[i - 1] == ")" and i - dp[i - 1] - 1 >= 0 and s[i - dp[i - 1] - 1] == "(":
dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2]
if dp[i] > res:
res = dp[i]
return res