知识点
- rolling state:遇到
"("+1,遇到")"-1,来判断是否平衡。中间任意时刻平衡被破坏(state < 0)都invalid。不同类型括号or找invalid括号的index时,需要用stack来辅助保存当下的状态- stack可以用来保存 x3(由近及远):
- 不同类型的括号
- 括号index
- 平行的前一层的sum/score
- backtracking:括号生成(考虑和permutation一样去重)
Rolling State
1614. 括号的最大嵌套深度(Easy)
思路:
- 忽略数字和符号,只统计圆括号:遇到
"("+1,遇到")"-1 深度 = max rolling state
代码:
class Solution:
def maxDepth(self, s: str) -> int:
depth = 0
ans = 0
for c in s:
if c == '(':
depth += 1
elif c == ')':
depth -= 1
ans = max(ans, depth)
return ans
921. 使括号有效的最少添加(Medium)
思路:
- 扫描中:一旦
rolling state < 0(不平衡),则补上一个"(",使得state再次平衡 - 扫描完:若最终的
rolling state > 0,表示"("过多,需要补充state个")"使之平衡 最终答案res = 扫描中补充的count个"(" + 扫描完后补充的state个的")"
代码:
class Solution:
def minAddToMakeValid(self, s: str) -> int:
state, count = 0, 0
for c in s:
if c == '(':
state += 1
elif c == ')':
state -= 1
if state < 0:
count += 1
state += 1 # state再次平衡
return count + state
Rolling State + Stack辅助 / two-pass
20. 有效的括号(Easy)
思路1:暴力解
- 如果当前char是左括号,则直接放入stack
- 如果当前char是右括号,则检查pop出的左括号和当前右括号是否匹配
代码1:
class Solution:
def isValid(self, s: str) -> bool:
stack = []
parentheses = {'(': ')', '[': ']', '{': '}'}
for char in s:
if char in parentheses:
stack.append(char)
else:
if stack == [] or parentheses[stack.pop()] != char:
return False
return len(stack) == 0
思路2:“预测结果”巧妙解 ❤️
- 把预测的结果放进去
代码2:
class Solution:
def isValid(self, s: str) -> bool:
stack = []
parentheses = {'(': ')', '[': ']', '{': '}'}
for char in s:
if char in parentheses:
stack.append(parentheses[char]) # 预测的结果
else:
if not stack or char != stack.pop():
return False
return len(stack) == 0
1249. 移除无效的括号(Medium)
思路1:
- 一次性找到所有的invalid的左右括号
- stack用来存左括号:左括号push,右括号pop
代码1:一次性删除
class Solution:
def minRemoveToMakeValid(self, s: str) -> str:
stack = []
indexes_to_remove = set()
for i, c in enumerate(s):
if c == '(':
stack.append(i) # 相当于state++
elif c == ')':
if stack: # 正常情况state--
stack.pop()
else: # invalid,当前有括号需要删除
indexes_to_remove.add(i)
# 有多余的左括号也需要全部删除
while stack:
indexes_to_remove.add(stack.pop())
# 剔除需要删除的括号
res = ''
for i, c in enumerate(s):
if i not in indexes_to_remove:
res += c
return res
思路2:Two Pass(non-stack)
- 从前往后pass 1:删除所有invalid ")"
- 从后往前pass 2:删除所有invalid "("
- 从左往右,扫描多余的")";从右往左,扫描多余的"("
代码2:
class Solution:
def minRemoveToMakeValid(self, s: str) -> str:
def removeHelper(toSave, toRemove, string: str):
state = 0
res = ''
for c in string:
if c == toSave:
state += 1
elif c == toRemove:
if state == 0: # 这个toRemove多余了
continue
state -= 1
res += c
return res
# pass 1: 从前往后,remove all invalid ")"
res = removeHelper('(', ')', s)
# pass 2: 从后往前,remove all invalid "("
res = removeHelper(')', '(', res[::-1])
return res[::-1]
856. 括号的分数(Medium)
思路1:stack + 递归
- 因为已经确定是「平衡括号字符串」,所以第一个"("必定在index=start=0处
- 把整个string拆分成两部分,用stack确定和第一个"("匹配的")"的位置end,并以此作为分割点
- 左半段:根据
len(s[start+1:end]),判定左半段 = 1 OR 2 * scoreOfParentheses(s[start+1:end]) - 右半段:直接递归处理
score2 = scoreOfParentheses(s[end:]) score_total = score_left + score_right
- 左半段:根据
代码1:
class Solution:
def scoreOfParentheses(self, s: str) -> int:
stack = []
start, end = 0, 0
for i, c in enumerate(s):
if c == '(':
stack.append(i)
else:
stack.pop()
if not stack:
end = i
break
score_left = 1 if end - start == 1 else 2 * self.scoreOfParentheses(s[start + 1:end])
score_right = 0 if end == len(s) - 1 else self.scoreOfParentheses(s[end + 1:])
return score_left + score_right
思路2.1:巧妙运用stack
- stack里存的是当前"("
前一层的score- ie:AB形式,遇到了B的第一个"(",此时需要把A的score压入stack
- 遇到")"的时候需要将前一层的score * 2并且和更前面的score求和,再保存会有stack
- ie:AB形式,遇到了B的最后一个")",
当前score_total = B的score * 2 + A的score
- ie:AB形式,遇到了B的最后一个")",
cur = 当前这一层的score
代码2.1:
class Solution:
def scoreOfParentheses(self, s: str) -> int:
stack = []
cur = 0
for c in s:
if c == '(':
stack.append(cur)
cur = 0 # 开始计算新的一层的score
else:
cur = stack.pop() + max(2 * cur, 1) # 当前这一层算完了
return cur
思路2.2:巧妙运用stack
- stack里存的是现在所有
当前frame的score
代码2.2:
class Solution:
def scoreOfParentheses(self, s: str) -> int:
stack = [0]
for c in s:
if c == '(':
stack.append(0)
else:
# 遇到AB的形式
B = stack.pop() # 当前正在处理的括号里的值
A = stack.pop() # 前面"平行的"A的值(已计算好)
stack.append(A + max(2 * B, 1))
return stack.pop()
32. 最长有效括号(Hard)
思路1:stack
- 一边判断括号有效性,一边统计最大长度
- 利用栈来判断括号合理性
- 遇到每个左括号,下标放入栈中,用于后续的匹配右括号
- 遇到每个右括号,先弹出栈顶来匹配
- 弹出后为空 -> 当前右括号没有被匹配,用其下标来更新栈底「最后一个没有被匹配的右括号的下标」,此时的栈顶就是栈底,因为是空栈
- 弹出不为空 -> 当前右括号被匹配,当前下标-栈顶 为当前右括号为结尾的最大长度子串
- 骚操作:保持栈底为「至今最后一个没有被匹配的右括号的下标」,用于计算最大长度,便于边界处理
代码1:
class Solution:
def longestValidParentheses(self, s: str) -> int:
stack = [-1]
ans = 0
for i, c in enumerate(s):
if c == '(':
stack.append(i)
else:
stack.pop()
if not stack: # 当前")"无法被匹配
stack.append(i)
else:
ans = max(ans, i - stack[-1])
return ans
思路2:Two Pass(non-stack)
套娃题,stack 或者 two-pass 都可以!
- pass 1(从左到右):
- 可以规避
")"过多无法匹配的情况 - 但无法规避出现
"("过多而无法更新ans的情况
- 可以规避
- pass 2(从右往左):
- 可以规避
"("过多无法匹配的情况 - 但无法规避出现
")"过多而无法更新ans的情况
- 可以规避
代码2:
class Solution:
def longestValidParentheses(self, s: str) -> int:
count_left, count_right, max_len = 0, 0, 0
# 从左往右,扫描多余的")"
for c in s:
if c == '(':
count_left += 1
else:
count_right += 1
if count_left == count_right: # 匹配
max_len = max(max_len, 2 * count_left)
elif count_left < count_right: # ")"过多,重新开始计数
count_left, count_right = 0, 0
count_left, count_right = 0, 0
# 从右往左,扫描多余的"("
for c in s[::-1]:
if c == '(':
count_left += 1
else:
count_right += 1
if count_left == count_right: # 匹配
max_len = max(max_len, 2 * count_left)
elif count_left > count_right: # "("过多,重新开始计数
count_left, count_right = 0, 0
return max_len
思路3:DP
- dp[i] = 以 s[i] 结尾的最长有效括号的长度
- 当 s[i] 为
(,必然不可能组成有效的括号,dp[i] = 0; - 当 s[i] 为
)- if
s[i-1] == (,那么dp[i] = dp[i-2] + 2; - ==if
s[i-1] == ) and s[i-dp[i-1] - 1] == (,那么dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2](因为可能还可以接上更前面的匹配括号)==
- if
代码3:
class Solution:
def longestValidParentheses(self, s: str) -> int:
dp = [0] * len(s) # dp[i] = 以s[i]结尾的最长有效括号长度
max_len = 0
for i in range(1, len(s)):
if s[i] == ')':
if s[i - 1] == '(':
dp[i] = (dp[i - 2] if i - 2 >= 0 else 0) + 2
else:
if s[i - dp[i - 1] - 1] == '(' and i - dp[i - 1] - 1 >= 0:
dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2] # 因为可能可以接上更前面的有效括号,所以要加上前面的长度
else:
dp[i] = 0
max_len = max(max_len, dp[i])
return max_len
Backtracking
题目中要求「返回所有结果」 => 显然是backtracking
301. 删除无效的括号(Hard)
思路:
- 先通过rolling state统计分别有多少个
“(”和“)”需要被remove - backtracking:
- 对于每一个括号,可以选择remove / not remove
- 对于每一个digit,不可remove
代码:
class Solution {
private Set<String> ans = new HashSet<>();
public List<String> removeInvalidParentheses(String s) {
int rml = 0, rmr = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') rml++;
else if (s.charAt(i) == ')') {
if (rml != 0) rml--;
else rmr++;
}
}
dfs(s, 0, rml, rmr, 0, new StringBuilder());
return new ArrayList<>(ans);
}
private void dfs(String s, int idx, int l, int r, int state, StringBuilder sb) {
// 终止条件
if (l < 0 || r < 0 || state < 0) return;
if (idx == s.length()) {
if (l == 0 && r == 0 && state == 0) ans.add(sb.toString());
return;
}
// 处理节点 + 递归
char c = s.charAt(idx);
if (c == '(') {
dfs(s, idx + 1, l - 1, r, state, sb); // remove "("
dfs(s, idx + 1, l, r, state + 1, sb.append(c)); // keep "("
sb.deleteCharAt(sb.length() - 1); // backtrack
} else if (c == ')') {
dfs(s, idx + 1, l, r - 1, state, sb); // remove ")"
dfs(s, idx + 1, l, r, state - 1, sb.append(c)); // keep ")"
sb.deleteCharAt(sb.length() - 1); // backtrack
} else { // digit
dfs(s, idx + 1, l, r, state, sb.append(c));
sb.deleteCharAt(sb.length() - 1); // backtrack
}
}
}
241. 为运算表达式设计优先级(Medium)
思路:DFS + 记忆化递归
- 在每个运算符号处做分割,分成
left和right两部分 - 对
left和right进行递归计算,并将其递归产生的值们分别两两相加/减/乘
代码:
class Solution:
def __init__(self):
self.memo = {}
def diffWaysToCompute(self, expression: str) -> List[int]:
def dfs(string) -> List[int]:
if string in self.memo:
return self.memo[string]
ans = []
for i, c in enumerate(string):
if c in '+-*':
left, right = dfs(string[:i]), dfs(string[i + 1:])
for l in left:
for r in right:
ans.append(eval(f'{l}{c}{r}'))
self.memo[string] = ans if ans else [int(string)]
return self.memo[string] # 如果expression中只有数字,完全没有运算符
return dfs(expression)
22. 括号生成(Medium)
思路:
- k组匹配的括号对 = 在 k-1组匹配的括号对 的每一个空档,插入一对
"()" - 用set去重
代码1: ❤️
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
def dfs(k, res):
if k == 1:
return res
ans = set()
for string in res:
for i in range(len(string)):
ans.add(string[:i] + '()' + string[i:])
return dfs(k - 1, ans)
return list(dfs(n, {'()'}))
代码2:
class Solution:
def __init__(self):
self.ans = set()
def generateParenthesis(self, n: int) -> List[str]:
def dfs(k, path) -> None:
if k == 1:
self.ans.add(path)
return
for i in range(len(path)):
new_path = path[:i] + '()' + path[i:]
dfs(k - 1, new_path)
dfs(n, '()')
return list(self.ans)
Reference: