LeetCode_32_困难_最长有效括号
1. 题目
给你一个只包含 '('
和 ')'
的字符串,找出最长有效(格式正确且连续)括号 子串 的长度。
示例 1:
输入:
s
=
s =
s= "(()"
输出:
2
2
2
解释:最长有效括号子串是 "()"
示例 2:
输入:
s
=
s =
s= ")()())"
输出:
4
4
4
解释:最长有效括号子串是 "()()"
示例 3:
输入:
s
=
s =
s= ""
输出:
0
0
0
提示:
- 0 < = s . l e n g t h < = 3 × 1 0 4 0 <= s.length <= 3 \times 10^4 0<=s.length<=3×104
-
s
[
i
]
s[i]
s[i] 为
'('
或')'
2. 思路及代码实现详解(Java)
2.1 动态规划
我们定义 dp [ i ] \textit{dp}[i] dp[i] 表示以下标 i i i 字符结尾的最长有效括号的长度。我们将 dp \textit{dp} dp 数组全部初始化为 0 0 0 。显然有效的子串一定以 ‘)’ \text{‘)’} ‘)’ 结尾,因此我们可以知道以 ‘(’ \text{‘(’} ‘(’ 结尾的子串对应的 dp \textit{dp} dp 值必定为 0 0 0 ,我们只需要求解 ‘)’ \text{‘)’} ‘)’ 在 dp \textit{dp} dp 数组中对应位置的值,返回其中最大的值即为所求。
我们从前往后遍历字符串求解 dp \textit{dp} dp 值,我们每两个字符检查一次:
- s [ i ] = ‘)’ s[i] = \text{‘)’} s[i]=‘)’ 且 s [ i − 1 ] = ‘(’ s[i - 1] = \text{‘(’} s[i−1]=‘(’,也就是字符串形如 “ … … ( ) ”“ … … ( ) ”“ … … ( ) ” “……()”“……()”“……()” “……()”“……()”“……()”,我们可以推出:
dp [ i ] = dp [ i − 2 ] + 2 \textit{dp}[i]=\textit{dp}[i-2]+2 dp[i]=dp[i−2]+2
我们可以进行这样的转移,是因为结束部分的 “()” 是一个有效子字符串,并且将之前有效子字符串的长度增加了 2 2 2 。
-
s
[
i
]
=
‘)’
s[i] = \text{‘)’}
s[i]=‘)’ 且
s
[
i
−
1
]
=
‘)’
s[i - 1] = \text{‘)’}
s[i−1]=‘)’,也就是字符串形如
“
…
…
)
)
”“
…
…
)
)
”“
…
…
)
)
”
“……))”“……))”“……))”
“……))”“……))”“……))”,我们可以推出:
如果有 s [ i − dp [ i − 1 ] − 1 ] = ‘(’ s[i - \textit{dp}[i - 1] - 1] = \text{‘(’} s[i−dp[i−1]−1]=‘(’,那么意味着 s [ i ] s[i] s[i] 与之对应,因此得到:
dp [ i ] = dp [ i − 1 ] + dp [ i − dp [ i − 1 ] − 2 ] + 2 \textit{dp}[i]=\textit{dp}[i-1]+\textit{dp}[i-\textit{dp}[i-1]-2]+2 dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2
具体分析,我们考虑如果倒数第二个 ‘)’ \text{‘)’} ‘)’ 是一个有效子字符串的一部分(记作 s u b s sub_s subs),对于最后一个 ‘)’ \text{‘)’} ‘)’ ,如果它是一个更长子字符串的一部分,那么它一定有一个对应的 ‘(’ \text{‘(’} ‘(’ ,且它的位置在倒数第二个 ‘)’ \text{‘)’} ‘)’ 所在的有效子字符串的前面(也就是 s u b s sub_s subs 的前面)。因此,如果子字符串 s u b s sub_s subs 的前面恰好是 ‘(’ \text{‘(’} ‘(’ ,那么我们就用 2 2 2 加上 s u b s sub_s subs 的长度( dp [ i − 1 ] \textit{dp}[i-1] dp[i−1])去更新 dp [ i ] \textit{dp}[i] dp[i]。同时,我们也会把有效子串 “ ( s u b s ) ” “(sub_s)” “(subs)” 之前的有效子串的长度也加上,也就是再加上 dp [ i − dp [ i − 1 ] − 2 ] \textit{dp}[i-\textit{dp}[i-1]-2] dp[i−dp[i−1]−2]。
最后的答案即为 dp \textit{dp} dp 数组中的最大值。该算法的时间复杂度为 O ( n ) O(n) O(n),其中 n n n 是字符串的长度,而空间开销主要是存储 d p dp dp 数组,为 O ( n ) O(n) O(n)。
class Solution {
public int longestValidParentheses(String s) {
int maxans = 0;
int[] dp = new int[s.length()];
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == ')') {
if (s.charAt(i - 1) == '(') {
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
} else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
}
maxans = Math.max(maxans, dp[i]);
}
}
return maxans;
}
}
执行用时:1 ms
消耗内存:41.28 MB
2.2 不需要额外空间的算法
在此方法中,我们利用两个计数器 left \textit{left} left 和 right \textit{right} right 。首先,我们从左到右遍历字符串,对于遇到的每个 ‘(’ \text{‘(’} ‘(’,我们增加 left \textit{left} left 计数器,对于遇到的每个 ‘)’ \text{‘)’} ‘)’ ,我们增加 right \textit{right} right 计数器。每当 left \textit{left} left 计数器与 right \textit{right} right 计数器相等时,我们计算当前有效字符串的长度,并且记录目前为止找到的最长子字符串。当 right \textit{right} right 计数器比 left \textit{left} left 计数器大时,我们将 left \textit{left} left 和 right \textit{right} right 计数器同时变回 0 0 0。
这样的做法贪心地考虑了以当前字符下标结尾的有效括号长度,每次当右括号数量多于左括号数量的时候之前的字符我们都扔掉不再考虑,重新从下一个字符开始计算,但这样会漏掉一种情况,就是遍历的时候左括号的数量始终大于右括号的数量,即 ( ( ) (() (() ,这种时候最长有效括号是求不出来的。
解决的方法也很简单,我们只需要从右往左遍历用类似的方法计算即可,只是这个时候判断条件反了过来:
- 当 left \textit{left} left 计数器比 right \textit{right} right 计数器大时,我们将 left \textit{left} left 和 right \textit{right} right 计数器同时变回 0 0 0
- 当 left \textit{left} left 计数器与 right \textit{right} right 计数器相等时,我们计算当前有效字符串的长度,并且记录目前为止找到的最长子字符串
这样我们就能涵盖所有情况从而求解出答案。
class Solution {
public int longestValidParentheses(String s) {
int left = 0, right = 0, maxlength = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * right);
} else if (right > left) {
left = right = 0;
}
}
left = right = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxlength = Math.max(maxlength, 2 * left);
} else if (left > right) {
left = right = 0;
}
}
return maxlength;
}
}
执行用时:1 ms
消耗内存:41.16 MB
题解来源:力扣官方题解