LeetCode解题 32:Longest Valid Parentheses(多解法:栈+动态规划+计数器)
Problem 32: Longest Valid Parentheses [Hard]
Given a string containing just the characters '('
and ')'
, find the length of the longest valid (well-formed) parentheses substring.
Example 1:
Input: “(()”
Output: 2
Explanation: The longest valid parentheses substring is “()”
Example 2:
Input: “)()())”
Output: 4
Explanation: The longest valid parentheses substring is “()()”
来源:LeetCode
解题思路
1. 栈
维护一个栈,起始压入-1,然后不停压入'('
和')'
的下标。当')'
遇到前面是'('
时就消去,并计算有效长度;当')'
前面没有'('
的时候,说明无效,需要清空并将')'
的下标压入最底层,用于之后计算有效长度。
具体思路:
-
创建栈sk,初始化压入-1。
-
遍历字符串s,遇到
'('
时直接压入,遇到'('
时需要分情况讨论:
i. 如果栈顶是'('
,将其pop()后计算当前有效长度并更新maxLen:
m a x L e n = max { m a x L e n , i − s k . p e e k ( ) } maxLen = \max\{maxLen, i - sk.peek()\} maxLen=max{maxLen,i−sk.peek()}
其中,i是当前遍历至的下标,sk.peek()是栈中消去的'('
的前一个位置的下标。ii. 如果栈顶不是
'('
,那么一定是-1
或者')'
,并且栈中一定只剩下了一个元素(因为之前所有的')'
都会被消去,只剩下一个用于计算有效长度)。此时需要将之前剩余的元素pop(),并将当前的')'
下标压入栈,用于后续计算长度。
时间复杂度为 O ( n ) O(n) O(n),空间复杂度最坏情况下为 O ( n ) O(n) O(n)。
运行结果:
2. 动态规划
使用动态规划思想。维护数组valLen[N],valLen[i]代表以字符串中s[i]结尾的最大有效长度。当s[i] = '('
时,valLen[i]一定为零;当s[i] = ')'
时,需要同时考虑s[i-1]。
遍历s
,valLen[i]根据s[i]和s[i-1]的情况可以由valLen[i-1]、valLen[i-2]、valLen[i-valLen[i-1]-2]等前项转换而来,具体转换公式为:
-
当s[i] =
'('
时, v a l L e n [ i ] = 0 valLen[i] = 0 valLen[i]=0。 -
当s[i] =
')'
时,需要考虑前一个字符,即s[i-1]。2.1 若s[i-1] =
'('
,也就是字符串为###()
$,则有效长度应为以s[i-2]结尾的最大有效长度再加2,转换公式为:
v a l L e n [ i ] = v a l L e n [ i − 2 ] valLen[i] = valLen[i-2] valLen[i]=valLen[i−2]
2.2 若s[i-1] =')'
,也就是字符串为###))
,则应该往前推valLen[i-1]个位置,看i-valLen[i-1]-1位置的字符是不是'('
,也就是看##x...))
中x位置,其中...)
的长度就是valLen[i-1],是一个有效字符串。如果x =
')'
,也就不能与s[i]的')'
匹配上,则 v a l L e n [ i ] = 0 valLen[i] = 0 valLen[i]=0。如果x =
'('
,则字符串为##(...))
,其中...)
的长度为valLen[i-1],还要加上新匹配上的两个括号,同时还要看x的前一个字符的最大有效长度(前一个字符为i - valLen[i-1] - 2),因此转换公式为:
v a l L e n [ i ] = v a l L e n [ i − 1 ] + 2 + v a l L e n [ i − v a l L e n [ i − 1 ] − 2 ] valLen[i] = valLen[i-1] + 2 + valLen[i-valLen[i-1]-2] valLen[i]=valLen[i−1]+2+valLen[i−valLen[i−1]−2] -
最后更新maxLen:
m a x L e n = max { m a x L e n , v a l L e n [ i ] } maxLen = \max\{maxLen, valLen[i]\} maxLen=max{maxLen,valLen[i]}
时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n)。
运行结果:
3. 计数器
创建两个计数器 l e f t left left和 r i g h t right right用于记录左括号和右括号的个数。
第一次从左到右遍历:
- 遇到
'('
时, l e f t left left++,遇到)
时, r i g h t right right++。 - 当 l e f t < r i g h t left < right left<right时,说明计数无效, l e f t left left和 r i g h t right right都清零。
- 当
l
e
f
t
=
r
i
g
h
t
left = right
left=right时,说明括号可以完全匹配,更新maxLen为:
m a x L e n = max { m a x L e n , l e f t + r i g h t } 。 maxLen = \max\{maxLen, left + right\}。 maxLen=max{maxLen,left+right}。
如果遍历结束后发现 l e f t > r i g h t left > right left>right说明中间可能有能够匹配上的部分因为左括号较多而无法满足 l e f t = r i g h t left = right left=right,因此需要反向遍历一次。
第二次从右到左遍历:
- 遇到
'('
时, l e f t left left++,遇到)
时, r i g h t right right++。 - 当 l e f t > r i g h t left > right left>right时,说明计数无效, l e f t left left和 r i g h t right right都清零。
- 当
l
e
f
t
=
r
i
g
h
t
left = right
left=right时,说明括号可以完全匹配,更新maxLen为:
m a x L e n = max { m a x L e n , l e f t + r i g h t } 。 maxLen = \max\{maxLen, left + right\}。 maxLen=max{maxLen,left+right}。
时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)。
运行结果:
要点:动态规划
、栈
Solution (Java)
1. 栈
class Solution {
public int longestValidParentheses(String s) {
int N = s.length();
if(N < 2) return 0;
int max = 0;
Stack<Integer> sk = new Stack<Integer>();
sk.push(-1);
for(int i = 0; i < N; i++){
if(s.charAt(i) == '('){
sk.push(i);
}
else{
// opt1
/*if(sk.size() == 1){
sk.pop();
sk.push(i);
}
else{ // sk.peek() = '('
sk.pop();
max = Math.max(max, i - sk.peek());
}*/
// opt2[*]
sk.pop();
if(sk.empty()) sk.push(i);
else{ // sk.pop() = '('
max = Math.max(max, i - sk.peek());
}
}
}
return max;
}
}
2. 动态规划
class Solution {
public int longestValidParentheses(String s) {
int N = s.length();
if(N < 2) return 0;
int max = 0;
int len;
int[] valLen = new int[N];
valLen[0] = 0;
for(int i = 1; i < N; i++){
if(s.charAt(i) == '(') valLen[i] = 0;
else{ // s[i] = ')'
if(s.charAt(i-1) == '('){
len = 2;
if(i-2 > 0) len += valLen[i-2];
valLen[i] = len;
max = Math.max(max, len);
}
else{
len = valLen[i-1];
if(i-len-1 < 0 || s.charAt(i-len-1) == ')') len = 0;
else{
if(i-len-2 < 0) len += 2;
else len += valLen[i-len-2] + 2;
}
valLen[i] = len;
max = Math.max(max, len);
}
}
}
return max;
}
}
3. 计数器
class Solution {
public int longestValidParentheses(String s) {
int N = s.length();
if(N < 2) return 0;
int max = 0;
int left = 0; // num of '('
int right = 0; // num of ')'
// from s[0] to s[N-1]
for(int i = 0; i < N; i++){
if(s.charAt(i) == '(') left++;
else right++;
if(left < right){
left = 0;
right = 0;
}
else if(left == right){
max = Math.max(max, left+right);
}
}
if(left < right) return max;
left = 0;
right = 0;
// from s[N-1] to s[0]
for(int i = N-1; i >= 0; i--){
if(s.charAt(i) == ')') right++;
else left++;
if(left > right){
left = 0;
right = 0;
}
else if(left == right){
max = Math.max(max, left+right);
}
}
return max;
}
}