题目:
Given a string containing just the characters '('
and ')'
, find the length of the longest valid (well-formed) parentheses substring.
For "(()"
, the longest valid parentheses substring is "()"
, which has length = 2.
Another example is ")()())"
, where the longest valid parentheses substring is "()()"
, which has length = 4.
思路:
这种求极值的问题很多情况下都可以采用动态规划加以解决,本题也不例外。可是对dp的不同定义却可以导致非常不同的时间复杂度。这里提供几种不同的思路:
1、二维动态规划:dp[i][j]表示从i到j之间的字符串是否可以构成合法的括弧对,则递推公式为:
1)如果dp[i+1][j-1] == true,并且s[i] == '(' && s[j] == ')',则dp[i][j] = true;
2)如果存在i < k < j,使得dp[i][k] == true && dp[k+1][j] == true,则dp[i][j] = true。
如果dp[i][j]为true,则可以更新当前得到的最长合法括号长度。可惜这种思路的时间复杂度高达O(n^3),空间复杂度为O(n^2),无法通过Leetcode上面的大数据。
2、一维动态规划+栈:利用栈来保证左括弧的数目不小于右括弧的数目(这是括弧对合法的必要条件)并且记录当前左括弧的位置。定义dp[i + 1]表示以i 为结尾的最长合法括弧对的长度。根据左右括弧匹配的性质,思路为:
1)如果字符串中当前字符为‘(’,则将其位置入栈,代表这个位置需要有一个匹配的右括弧;
2)如果字符串中当前字符为')',则检查栈:
a)如果此时栈为空,说明当前段的字符串是不合法的;
b)如果此时栈不为空,则说明当前的右括弧可以被匹配,并且dp[i+1] = (i - j + 1) + dp[j]。其中(i - j + 1)表示从当前字符到栈顶元素的长度,而dp[j]表示栈顶元素之前的合法子字符串的长度。该思路的时间复杂度为O(n),空间复杂度为O(n)。
3、一维动态规划:仔细推敲2的思路,发现实际上对于dp[i+1]而言,通过dp[i]可以推导出能够和s[i] == ')' 匹配的左括弧的索引值为(i - dp[i] - 1)。那么我们就不再需要开辟栈空间了,而是可以直接推导出dp[i+1]和dp[i]之间的递推关系:dp[i + 1] = dp[i - dp[i] - 1] + dp[i] + 2。该算法的时间和空间复杂度同2。
4、栈:事实上')'也是可以作为分界符入栈的。如果当前字符为')',则试图在栈中寻找和它匹配的'('的左括弧。如果找到相对应的左括弧,则首先弹栈(为什么要先弹栈? 考虑()()的情况),然后可以知道以‘)’结尾的合法括弧对的长度为st.empty() ? i + 1 : i - st.top()。而不以该')'结尾的合法最长合法括弧对的长度为在此前已经被记录下来了,所以此时只需要更新其值即可。一旦无法找到和当前')'匹配的左括弧,则将该')'入栈作为分界符,使得后来的右括弧无法再与当前栈中非栈顶的左括弧相匹配。这个思路确实很精妙,虽然 理论上时间复杂度和空间复杂度仍然为O(n),但事实上在大多数情况下空间复杂度会远小于O(n)。
代码:
一维动态规划+栈:
class Solution {
public:
int longestValidParentheses(string s)
{
if(s.size() == 0)
return 0;
stack<int> st;
vector<int> dp(s.size() + 1, 0);
int ret = 0, len= 0;
for(int i = 0; i < s.size(); i++)
{
if(s[i] == '(')
{
st.push(i);
}
else
{
if(!st.empty())
{
dp[i + 1] = (i - st.top() + 1) + dp[st.top()];
st.pop();
ret = max(dp[i + 1], ret);
}
}
}
return ret;
}
};
一维动态规划:
class Solution {
public:
int longestValidParentheses(string s)
{
int ret = 0;
// dp[i] means the length of the longest valid parenthese that ends with s[i]
vector<int> dp(s.size() + 1, 0);
for(int i = 1; i < s.size(); i++)
{
if(s[i]=='(')
continue;
// now we know s[i] == ')'
// (i - dp[i] - 1) is the index of the '(' that will match with s[i]
if(i - dp[i] - 1 >= 0 && s[i - dp[i] - 1] =='(')
dp[i + 1] = dp[i - dp[i] - 1] + dp[i] + 2;
ret = max(ret, dp[i + 1]);
}
return ret;
}
};
栈:
class Solution {
public:
int longestValidParentheses(string s)
{
int ret = 0;
stack<int> st;
for(int i = 0; i < s.size(); i++)
{
if(s[i]=='(')
{
st.push(i);
}
else // s[i] == ')'
{
if(!st.empty() && s[st.top()]=='(')
{
st.pop();
ret = max(st.empty() ? i + 1 : i - st.top(), ret);
}
else
{
st.push(i);
}
}
}
return ret;
}
};