Longest Valid Parentheses 最长合法括号
一、问题描述
给一个只由’(‘和’)'组成的字符串,找出其中最长的连续合法子串长度。(来源Leetcode)
样例
Input:(()
Output:2
Input: )()())
Output: 4
Input: ((())
Output: 4
二、解决方案
很明显,可以用动态规划,我也是这么想的。
1. 我的方案
构建dp[len][len], dp[i][j]表示以i开头,j结尾的子串中最长子串。s表示字符串,然后给出递推式
d
p
[
i
]
[
j
]
=
{
2
,
if i+1 = j and s[i] = ’(’ and s[j] = ’)’
d
p
[
i
+
1
]
[
j
−
1
]
+
2
,
if dp[i+1][j-1] > 0 and s[i] = ’(’ and s[j] = ’)’
d
p
[
k
+
1
]
[
j
]
+
d
p
[
i
]
[
k
]
,
if dp[i][k] > 0 and dp[k+1][j] > 0,
k
∈
(
i
,
j
)
dp[i][j] = \begin {cases} 2, & \text{if i+1 = j and s[i] = '(' and s[j] = ')'}\\ dp[i+1][j-1] + 2, & \text{if dp[i+1][j-1] > 0 and s[i] = '(' and s[j] = ')'}\\ dp[k+1][j] + dp[i][k], & \text{if dp[i][k] > 0 and dp[k+1][j] > 0, } k \in (i, j)\\ \end {cases}
dp[i][j]=⎩⎪⎨⎪⎧2,dp[i+1][j−1]+2,dp[k+1][j]+dp[i][k],if i+1 = j and s[i] = ’(’ and s[j] = ’)’if dp[i+1][j-1] > 0 and s[i] = ’(’ and s[j] = ’)’if dp[i][k] > 0 and dp[k+1][j] > 0, k∈(i,j)
注意,循环的时候注意
i
∈
(
l
e
n
,
0
]
,
j
∈
[
i
+
1
,
l
e
n
)
,
k
∈
(
i
,
j
)
i \in (len, 0], j \in[i+1, len), k \in (i, j)
i∈(len,0],j∈[i+1,len),k∈(i,j)
class Solution {
public:
int longestValidParentheses(string s) {
int len = s.size();
vector<vector<int> > dp(len, vector<int>(len, 0));
int max = 0;
for(int i = len - 1; i >= 0; -- i){
for(int j = i + 1; j < len; j += 2){
if(i + 1 == j && s[i] == '(' && s[j] == ')')
dp[i][j] = 2;
else if(dp[i+1][j-1] > 0 && s[i] == '(' && s[j] == ')')
dp[i][j] = dp[i+1][j-1] + 2;
else{
for(int k = i + 1; k < j; k += 2){
if(dp[i][k] > 0 && dp[k+1][j] > 0){
dp[i][j] = dp[k+1][j] + dp[i][k];
break;
}
}
}
if(max < dp[i][j])
max = dp[i][j];
}
}
return max;
}
};
结果分析,虽说用了动态规划的思想,但是明显时间和空间复杂度过高,尽管有一定技巧性,但本质上是 O ( n 3 ) , O ( n 2 ) O(n^3), O(n^2) O(n3),O(n2), 还不如暴力搜索,至少暴力搜索的空间复杂度是 O ( n ) O(n) O(n)。
2. Leetcode上的解决方案
原文链接,以下内容主要是来源于LeetCode上的解决方案,我主要是翻译了一下,主要分为暴力,动态规划,堆栈,无需额外空间法。
2.1暴力(Brute Force)
找出所有偶数子串,然后使用堆栈判断是否是合法括号。
时间复杂度和空间复杂度为
O
(
n
3
)
,
O
(
n
)
O(n^3), O(n)
O(n3),O(n)
public class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<Character>();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push('(');
} else if (!stack.empty() && stack.peek() == '(') {
stack.pop();
} else {
return false;
}
}
return stack.empty();
}
public int longestValidParentheses(String s) {
int maxlen = 0;
for (int i = 0; i < s.length(); i++) {
for (int j = i + 2; j <= s.length(); j+=2) {
if (isValid(s.substring(i, j))) {
maxlen = Math.max(maxlen, j - i);
}
}
}
return maxlen;
}
}
2.2 动态规划(Dynamic Programming)
只需一个一维动态数组,dp[i]表示,以第i个字符结尾的合法子串长度。换句话说,合法子串包括第i个字符。时间复杂度和空间复杂度分别为
O
(
n
)
,
O
(
n
)
O(n), O(n)
O(n),O(n)。下面给出递推式
d
p
[
i
]
=
{
d
p
[
i
−
2
]
+
2
,
if i is even and s[i-1] = ’(’ and s[i] = ’)’
d
p
[
i
−
1
]
+
d
p
[
i
−
d
p
[
i
−
1
]
−
2
]
+
2
,
if i is even and s[i-1] = ’)’ and s[i] = ’)’ and s[i−dp[i−1]−1]=’(’
0
,
others
dp[i] = \begin {cases} dp[i-2] + 2, & \text{if i is even and s[i-1] = '(' and s[i] = ')'}\\ dp[i-1] + dp[i - dp[i-1] - 2] + 2, & \text{if i is even and s[i-1] = ')' and s[i] = ')' and s[i−dp[i−1]−1]='('}\\ 0, & \text{others}\\ \end {cases}
dp[i]=⎩⎪⎨⎪⎧dp[i−2]+2,dp[i−1]+dp[i−dp[i−1]−2]+2,0,if i is even and s[i-1] = ’(’ and s[i] = ’)’if i is even and s[i-1] = ’)’ and s[i] = ’)’ and s[i−dp[i−1]−1]=’(’others
public 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;
}
}
2.3 栈(Using Stack)
新建一个Stack,
- 压入 -1;
- 对每个字符,如果是’(’,压入该索引,
- 如果是’)’, 弹出栈顶元素,此时用该索引,减去弹出元素之后的栈顶元素,即为当前合法子串长度。如果栈为空,则将’)'的索引压入栈中。
该算法的时间复杂度和空间复杂度为 O ( n ) , O ( n ) O(n), O(n) O(n),O(n)
public class Solution {
public int longestValidParentheses(String s) {
int maxans = 0;
Stack<Integer> stack = new Stack<>();
stack.push(-1);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(i);
} else {
stack.pop();
if (stack.empty()) {
stack.push(i);
} else {
maxans = Math.max(maxans, i - stack.peek());
}
}
}
return maxans;
}
}
2.4 无需额外空间(Without extra space)
用两个变量left和right存储左右括号数量,当left == right,表示当前最大子串长度,当right > left,表示遇到不合法,两个变量置0。当遇到"(()",此时该方法失效,因此从左往右扫,然后从右往左扫,即可完美解决所有情况。
该算法的时间复杂度和空间复杂度为
O
(
n
)
,
O
(
1
)
O(n), O(1)
O(n),O(1)
public 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;
}
}