LeetCode 32 题“最长有效括号”是括号类问题的进阶题型。给定一个只包含 '(' 和 ')' 的字符串,要求找出最长的连续有效括号子串的长度。关键点在于:子串必须连续,且括号要正确匹配。
常见解法有栈、动态规划、双指针等。其中动态规划的思路最贴近“逐步构建最优解”的思维过程,非常适合用来锻炼状态定义与转移的能力。
核心状态定义
我们定义一个长度为 n+1 的数组 f,其中:
- f[0] = 0(空字符串)
- f[i+1] 表示以 s[i](第 i 个字符,下标从 0 开始)结尾的最长有效括号子串长度
为什么长度设为 n+1?这样可以让 f[i+1] 对应 s[i],避免频繁讨论 i-1 越界的问题
遍历过程中:
- 如果 s[i] == '(':以左括号结尾不可能形成新的有效括号对,因此 f[i+1] = 0
- 如果 s[i] == ')':才有形成有效括号的可能,需要分类讨论
状态转移:遇到 ')' 时的两种情况
情况一:前一个字符 s[i-1] == '('
这意味着 "...( )",当前 ')' 可以与紧邻的 '(' 直接匹配,形成一对 "()"。 此时,以 i 结尾的有效长度 = 以 i-2 结尾的有效长度 + 2
示例:s = "()()"
- i=1 时,s[1]=')',s[0]='(' → f[2] = f[0] + 2 = 2
- i=3 时,s[3]=')',s[2]='(' → f[4] = f[2] + 2 = 4
情况二:前一个字符 s[i-1] == ')'
这意味着当前 ')' 的前面可能形成一段最长有效括号(长度为 f[i])。 贪心地考虑,我们需要“跳过”这段最长有效括号是最优的,看看它前面是否还有一个 '(' 可以与当前 ')' 匹配。
计算跳过后的位置:pos = i - f[i] - 1 (i - 1 是前一个位置,减去 f[i] 就是跳过以 i-1 结尾的那段有效括号,再减 1 得到前一个位置)
注意判断pos >= 0 ,因为我们需要判断 s[pos]
- 如果 pos < 0:越界,无法匹配 → f[i+1] = 0
- 如果 pos >= 0 且 s[pos] == ')':前面也是右括号,无法匹配 → f[i+1] = 0
- 如果 pos >= 0 且 s[pos] == '(':可以匹配!
此时,以 i 结尾的有效长度 = 当前匹配的 2 + 中间那段有效括号长度 f[i] + pos 之前能形成的最长有效括号 f[pos]
示例:s = "(()())"
-
i=2:')',s[1]='(' → 情况一,f[3] = f[1] + 2 = 2
-
i=5:')',s[4]=')' → 情况二
- f[5] = 2(以位置4结尾的长度)
- pos = 5 - 2 - 1 = 2
- s[2] = ')'?不是,是 '(' → 可以匹配
- f[6] = f[5] + 2 + f[2] = 2 + 2 + 0 = 4?等一下,应该是 6!
仔细看:f[pos] 是 f[3](因为 pos=2,对应 f[3]=2) 正确计算:f[6] = f[5] + 2 + f[pos] = 2 + 2 + f[3] = 2 + 2 + 2 = 6
完美!整个 "(()())" 都被覆盖。
完整代码实现
C++
class Solution {
public:
int longestValidParentheses(string s) {
int n = s.size();
vector<int> f(n + 1, 0);
for (int i = 0; i < n; i++) {
if (s[i] == ')') {
if (i >= 1 && s[i - 1] == '(') {
f[i + 1] = f[i - 1] + 2;
} else if (i >= 1 && s[i - 1] == ')') {
int pos = i - f[i] - 1;
if (pos >= 0 && s[pos] == '(') {
f[i + 1] = f[i] + 2 + f[pos];
}
}
}
}
return ranges::max(f);
}
};
Go
func longestValidParentheses(s string) int {
n := len(s)
f := make([]int, n+1)
for i, ss := range s {
if ss == ')' {
if i >= 1 && s[i-1] == '(' {
f[i+1] = f[i-1] + 2
} else if i >= 1 && s[i-1] == ')'{
pos := i - f[i] - 1
if pos >= 0 && s[pos] == '(' {
f[i+1] = f[i] + 2 + f[pos]
}
}
}
}
return slices.Max(f);
}
Python
class Solution:
def longestValidParentheses(self, s: str) -> int:
n = len(s)
f = [0] * (n + 1)
for i, ch in enumerate(s):
if ch == ')':
if i > 0 and s[i - 1] == '(':
f[i + 1] = f[i - 1] + 2
elif s[i - 1] == ')':
pos = i - f[i] - 1
if pos >= 0 and s[pos] == '(':
f[i + 1] = f[i] + 2 + f[pos]
return max(f)
JavaScript
/**
* @param {string} s
* @return {number}
*/
var longestValidParentheses = function(s) {
const n = s.length;
const f = Array(n+1).fill(0);
for (let i = 0; i < n; i++) {
if (s[i] == ')') {
if (i >= 1 && s[i-1] == '(') {
f[i+1] = f[i-1] + 2;
} else if (i >= 1 && s[i-1] == ')') {
const pos = i - f[i] - 1;
if (pos >= 0 && s[pos] == '(') {
f[i+1] = f[i] + 2 + f[pos]
}
}
}
}
return Math.max(...f)
};