主题
什么是有效括号?
可以从消消乐的角度理解,每次消除一对相邻的左括号和右括号,不断消除,如果最终变成空串,则(消除之前)是有效括号。
比如
什么不是有效括号?
- 某些左括号没有对应的右括号。例如
,缺失了一个右括号。 - 某些右括号没有对应的左括号。例如
,缺失了一个左括号。
栈 + 配对标记
从左到右遍历字符串
为方便找到左侧最近的未配对的左括号,我们可以用一个栈保存遍历过的左括号的下标:
- 遇到左括号,把其下标入栈。
- 遇到右括号,且栈非空,那么弹出栈顶。这样遍历到下一个右括号时,栈顶总是最近的未配对的左括号的下标。
最后,最长连续已配对标记的长度,就是最长有效括号的长度。做法同 485. 最大连续 1 的个数,我的题解。
python
class Solution:
def longestValidParentheses(self, s: str) -> int:
n = len(s)
is_valid = [False] * n
st = [] # 未配对的左括号的下标
# 标记哪些括号是配对的
for i, ch in enumerate(s):
if ch == '(':
st.append(i) # 保存左括号的下标
elif st: # 右括号与栈顶的左括号配对
is_valid[i] = is_valid[st.pop()] = True
# 最长有效括号即为 is_valid 中的最长连续 True
ans = cnt = 0
for b in is_valid:
if b:
cnt += 1 # 连续 True 的个数
ans = max(ans, cnt)
else:
cnt = 0 # 重置计数器
return anscpp
// C++ 版待补充cpp
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.size();
vector<int8_t> is_valid(n);
stack<int> st; // 未配对的左括号的下标
// 标记哪些括号是配对的
for (int i = 0; i < n; i++) {
if (s[i] == '(') {
st.push(i); // 保存左括号的下标
} else if (!st.empty()) { // 右括号与栈顶的左括号配对
is_valid[i] = is_valid[st.top()] = true;
st.pop();
}
}
// 最长有效括号即为 is_valid 中的最长连续 true
int ans = 0, cnt = 0;
for (int i = 0; i < n; i++) {
if (is_valid[i]) {
cnt++; // 连续 true 的个数
ans = max(ans, cnt);
} else {
cnt = 0; // 重置计数器
}
}
return ans;
}
};复杂度分析
- 时间复杂度:
,其中 是 的长度。 - 空间复杂度:
。
优化:一次遍历
既然栈中保存的是未配对的下标,那么从栈顶加一的位置到
特殊情况:
- 例如
,其中 是永远无法配对的右括号。把这种括号的下标入栈,从而保证「栈顶加一」是有效括号的左端点。 - 如果栈为空呢?此时有效括号的左端点是
。我们可以在一开始,往栈中添加一个 ,这样可以兼容「栈顶加一」是有效括号的左端点,从而简化代码逻辑。
python
class Solution:
def longestValidParentheses(self, s: str) -> int:
st = [-1] # 未配对括号的下标,其中栈底元素表示永远无法配对的括号下标
ans = 0
for i, ch in enumerate(s):
if ch == '(':
st.append(i) # 保存左括号的下标
elif len(st) > 1:
st.pop() # 右括号与栈顶的左括号配对
ans = max(ans, i - st[-1]) # 从 st[-1]+1 到 i 都已配对,长为 i - st[-1]
else: # s[i] 是永远无法配对的右括号
st[0] = i # 替换栈底
return anscpp
// C++ 版待补充cpp
class Solution {
public:
int longestValidParentheses(string s) {
stack<int> st; // 未配对括号的下标
st.push(-1); // 栈底元素表示永远无法配对的括号下标
int ans = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(') {
st.push(i); // 保存左括号的下标
} else if (st.size() > 1) {
st.pop(); // 右括号与栈顶的左括号配对
ans = max(ans, i - st.top()); // 从 st.top()+1 到 i 都已配对,长为 i - st.top()
} else { // s[i] 是永远无法配对的右括号
st.top() = i; // 替换栈底
}
}
return ans;
}
};复杂度分析
- 时间复杂度:
,其中 是 的长度。 - 空间复杂度:
。
优化:O(1) 空间复杂度
对于有效括号字符串,左括号和右括号的个数是相等的。
能不能只记录左右括号的个数,并在左括号个数等于右括号个数时,更新答案?
比如
| 左括号个数 | 右括号个数 | 说明 | ||
|---|---|---|---|---|
| 个数相同,有效括号长度为 | ||||
| 右括号太多了,重置个数为 | ||||
| 个数相同,有效括号长度为 |
又比如
| 左括号个数 | 右括号个数 | 说明 | ||
|---|---|---|---|---|
| 个数相同,有效括号长度为 | ||||
| 无法判断有效括号长度(见下面解释) |
设
- 如果
,有效括号长度为 。 - 如果
,右括号太多了,重置计数器 。 - 如果
,我们无法只根据 和 就确定有效括号的长度。比如 , ,那么 是 还是 ?有效括号长为 还是 ?如何解决 这种左括号很多的情况呢?
把
python
class Solution:
def solve(self, s: Iterable[str], left_ch: str) -> int:
ans = left = right = 0
for ch in s:
if ch == left_ch:
left += 1
else:
right += 1
if left < right: # 右括号太多了,重置计数器
left = right = 0
elif left == right:
ans = max(ans, right * 2)
return ans
def longestValidParentheses(self, s: str) -> int:
return max(self.solve(s, '('), self.solve(reversed(s), ')')) # reversed 是 O(1) 的cpp
// C++ 版待补充cpp
class Solution {
public:
int longestValidParentheses(string s) {
int ans = 0, left = 0, right = 0;
for (char ch : s) {
if (ch == '(') {
left++;
} else {
right++;
}
if (left < right) { // 右括号太多了,重置计数器
left = right = 0;
} else if (left == right) {
ans = max(ans, right * 2);
}
}
left = right = 0;
for (int i = s.size() - 1; i >= 0; i--) {
if (s[i] == ')') {
left++;
} else {
right++;
}
if (left < right) {
left = right = 0;
} else if (left == right) {
ans = max(ans, right * 2);
}
}
return ans;
}
};复杂度分析
- 时间复杂度:
,其中 是 的长度。 - 空间复杂度:
。
专题训练
见下面数据结构题单的「§3.4 合法括号字符串」。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府