难度困难
题目描述
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号 子串 的长度。
左右括号匹配,即每个左括号都有对应的右括号将其闭合的字符串是格式正确的,比如 "(()())"。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:
输入:s = ""
输出:0
提示:
0 <= s.length <= 3 * 104 s[i] 为 '(' 或 ')'
解法
方法一:动态规划
思考
枚举每个子串再用栈判定是否有效,时间复杂度为 \(O(n^2)\) 乃至 \(O(n^3)\)。 \(n \le 3 \times 10^4\),无法通过。
大量子串互相重叠:以某一位置结尾的最长有效括号,可以由更短的有效段拼接得到。因此可以按结尾位置递推。
有效括号串只能以右括号结尾。若 \(s[i-1]\) 与前一位配成一对,长度即为再往前那段加上 \(2\);若前一位也是右括号,则先跨过已经配好的一段,再判断这段之前是否还能配上一个左括号。我们可以定义 \(f[i]\) 为以 \(s[i-1]\) 结尾的最长有效长度,按上述两种配对方式转移,答案取 \(\max f[i]\)。
我们定义 \(f[i]\) 表示以 \(s[i-1]\) 结尾的最长有效括号的长度,那么答案就是 \(\max\limits_{i=1}^n f[i]\)。
- 如果 \(s[i-1]\) 是左括号,那么以 \(s[i-1]\) 结尾的最长有效括号的长度一定为 \(0\),因此 \(f[i] = 0\)。
- 如果 \(s[i-1]\) 是右括号,有以下两种情况:
- 如果 \(s[i-2]\) 是左括号,那么以 \(s[i-1]\) 结尾的最长有效括号的长度为 \(f[i-2] + 2\)。
- 如果 \(s[i-2]\) 是右括号,那么以 \(s[i-1]\) 结尾的最长有效括号的长度为 \(f[i-1] + 2\),但是还需要考虑 \(s[i-f[i-1]-2]\) 是否是左括号,如果是左括号,那么以 \(s[i-1]\) 结尾的最长有效括号的长度为 \(f[i-1] + 2 + f[i-f[i-1]-2]\)。
因此,我们可以得到状态转移方程:
\[ \begin{cases} f[i] = 0, & \textit{if } s[i-1] = '(',\\ f[i] = f[i-2] + 2, & \textit{if } s[i-1] = ')' \textit{ and } s[i-2] = '(',\\ f[i] = f[i-1] + 2 + f[i-f[i-1]-2], & \textit{if } s[i-1] = ')' \textit{ and } s[i-2] = ')' \textit{ and } s[i-f[i-1]-2] = '(',\\ \end{cases} \]
最后返回 \(\max\limits_{i=1}^n f[i]\) 即可。
时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。其中 \(n\) 为字符串的长度。
1
2
3
4
5
6
7
8
9
10
11
12
13 | class Solution:
def longestValidParentheses(self, s: str) -> int:
n = len(s)
f = [0] * (n + 1)
for i, c in enumerate(s, 1):
if c == ")":
if i > 1 and s[i - 2] == "(":
f[i] = f[i - 2] + 2
else:
j = i - f[i - 1] - 1
if j and s[j - 1] == "(":
f[i] = f[i - 1] + 2 + f[j - 1]
return max(f)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | class Solution {
public int longestValidParentheses(String s) {
int n = s.length();
int[] f = new int[n + 1];
int ans = 0;
for (int i = 2; i <= n; ++i) {
if (s.charAt(i - 1) == ')') {
if (s.charAt(i - 2) == '(') {
f[i] = f[i - 2] + 2;
} else {
int j = i - f[i - 1] - 1;
if (j > 0 && s.charAt(j - 1) == '(') {
f[i] = f[i - 1] + 2 + f[j - 1];
}
}
ans = Math.max(ans, f[i]);
}
}
return ans;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | class Solution {
public:
int longestValidParentheses(string s) {
int n = s.size();
int f[n + 1];
memset(f, 0, sizeof(f));
for (int i = 2; i <= n; ++i) {
if (s[i - 1] == ')') {
if (s[i - 2] == '(') {
f[i] = f[i - 2] + 2;
} else {
int j = i - f[i - 1] - 1;
if (j && s[j - 1] == '(') {
f[i] = f[i - 1] + 2 + f[j - 1];
}
}
}
}
return *max_element(f, f + n + 1);
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | func longestValidParentheses(s string) int {
n := len(s)
f := make([]int, n+1)
for i := 2; i <= n; i++ {
if s[i-1] == ')' {
if s[i-2] == '(' {
f[i] = f[i-2] + 2
} else if j := i - f[i-1] - 1; j > 0 && s[j-1] == '(' {
f[i] = f[i-1] + 2 + f[j-1]
}
}
}
return slices.Max(f)
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | function longestValidParentheses(s: string): number {
const n = s.length;
const f: number[] = new Array(n + 1).fill(0);
for (let i = 2; i <= n; ++i) {
if (s[i - 1] === ')') {
if (s[i - 2] === '(') {
f[i] = f[i - 2] + 2;
} else {
const j = i - f[i - 1] - 1;
if (j && s[j - 1] === '(') {
f[i] = f[i - 1] + 2 + f[j - 1];
}
}
}
}
return Math.max(...f);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | impl Solution {
pub fn longest_valid_parentheses(s: String) -> i32 {
let mut ans = 0;
let mut f = vec![0; s.len() + 1];
let chars = s.as_bytes();
for i in 2..=s.len() {
if chars[i - 1] == b')' {
if chars[i - 2] == b'(' {
f[i] = f[i - 2] + 2;
} else if (i as i32) - f[i - 1] - 1 > 0
&& chars[i - (f[i - 1] as usize) - 2] == b'('
{
f[i] = f[i - 1] + 2 + f[i - (f[i - 1] as usize) - 2];
}
ans = ans.max(f[i]);
}
}
ans
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | /**
* @param {string} s
* @return {number}
*/
var longestValidParentheses = function (s) {
const n = s.length;
const f = new Array(n + 1).fill(0);
for (let i = 2; i <= n; ++i) {
if (s[i - 1] === ')') {
if (s[i - 2] === '(') {
f[i] = f[i - 2] + 2;
} else {
const j = i - f[i - 1] - 1;
if (j && s[j - 1] === '(') {
f[i] = f[i - 1] + 2 + f[j - 1];
}
}
}
}
return Math.max(...f);
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | public class Solution {
public int LongestValidParentheses(string s) {
int n = s.Length;
int[] f = new int[n + 1];
int ans = 0;
for (int i = 2; i <= n; ++i) {
if (s[i - 1] == ')') {
if (s[i - 2] == '(') {
f[i] = f[i - 2] + 2;
} else {
int j = i - f[i - 1] - 1;
if (j > 0 && s[j - 1] == '(') {
f[i] = f[i - 1] + 2 + f[j - 1];
}
}
ans = Math.Max(ans, f[i]);
}
}
return ans;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | class Solution {
/**
* @param string $s
* @return integer
*/
function longestValidParentheses($s) {
$n = strlen($s);
$f = array_fill(0, $n + 1, 0);
for ($i = 1; $i <= $n; $i++) {
if ($s[$i - 1] == ')') {
if ($i > 1 && $s[$i - 2] == '(') {
$f[$i] = $f[$i - 2] + 2;
} else {
$j = $i - $f[$i - 1] - 1;
if ($j && $s[$j - 1] == '(') {
$f[$i] = $f[$i - 1] + 2 + $f[$j - 1];
}
}
}
}
return max($f);
}
}
|
方法二:使用栈
思考
方法一已经是 \(O(n)\) 时间,但转移需区分「前一位是否为左括号」以及「跨过一段有效括号再向前配对」,下标边界容易遗漏。
我们需要的并非更低的复杂度,而是更贴合配对过程的记录方式:尚未匹配的左括号下标,以及上一段失效从何处开始。栈恰好维护这两类信息:栈底先放入 \(-1\) 作为基准,遇左括号压入下标,遇右括号弹出;若栈空则当前位置成为新基准,否则用栈顶更新长度。
- 使用栈来存储左括号的索引,栈底元素初始化为
-1,用于辅助计算有效括号的长度。 - 遍历字符串,对于每个字符:
- 如果是左括号,将当前位置压入栈。
- 如果是右括号,弹出栈顶元素表示匹配了一个左括号。
- 如果栈为空,说明当前右括号无法匹配,将当前位置压入栈作为新的起点。
- 如果栈不为空,计算当前有效括号子串的长度,更新最大长度。
- 最终返回最大长度。
总结:这个算法的关键在于维护一个线,栈内存放的是左括号的索引,通过弹出和压入的操作来更新有效括号子串的长度。
时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。其中 \(n\) 为字符串的长度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | class Solution:
def longestValidParentheses(self, s: str) -> int:
stack = [-1]
ans = 0
for i in range(len(s)):
if s[i] == '(':
stack.append(i)
else:
stack.pop()
if not stack:
stack.append(i)
else:
ans = max(ans, i - stack[-1])
return ans
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | func longestValidParentheses(s string) int {
ans := 0
stack := []int{-1}
for i, v := range s {
if v == '(' {
stack = append(stack, i)
} else {
stack = stack[:len(stack)-1]
if len(stack) == 0 {
stack = append(stack, i)
} else {
if ans < i-stack[len(stack)-1] {
ans = i - stack[len(stack)-1]
}
}
}
}
return ans
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | function longestValidParentheses(s: string): number {
let max_length: number = 0;
const stack: number[] = [-1];
for (let i = 0; i < s.length; i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else {
stack.pop();
if (stack.length === 0) {
stack.push(i);
} else {
max_length = Math.max(max_length, i - stack[stack.length - 1]);
}
}
}
return max_length;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | impl Solution {
pub fn longest_valid_parentheses(s: String) -> i32 {
let mut stack = vec![-1];
let mut res = 0;
for (i, c) in s.bytes().enumerate() {
if c == b'(' {
stack.push(i as i32);
} else {
stack.pop().unwrap();
if stack.is_empty() {
stack.push(i as i32);
} else {
res = std::cmp::max(res, (i as i32) - stack.last().unwrap());
}
}
}
res
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | /**
* @param {string} s
* @return {number}
*/
var longestValidParentheses = function (s) {
let ans = 0;
const stack = [-1];
for (i = 0; i < s.length; i++) {
if (s.charAt(i) === '(') {
stack.push(i);
} else {
stack.pop();
if (stack.length === 0) {
stack.push(i);
} else {
ans = Math.max(ans, i - stack[stack.length - 1]);
}
}
}
return ans;
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | class Solution {
/**
* @param string $s
* @return integer
*/
function longestValidParentheses($s) {
$stack = [];
$maxLength = 0;
array_push($stack, -1);
for ($i = 0; $i < strlen($s); $i++) {
if ($s[$i] === '(') {
array_push($stack, $i);
} else {
array_pop($stack);
if (empty($stack)) {
array_push($stack, $i);
} else {
$length = $i - end($stack);
$maxLength = max($maxLength, $length);
}
}
}
return $maxLength;
}
}
|