BM77 最长的括号子串
知识点字符串动态规划栈
描述
给出一个长度为 n 的,仅包含字符 '(' 和 ')' 的字符串,计算最长的格式正确的括号子串的长度。
例1: 对于字符串 "(()" 来说,最长的格式正确的子串是 "()" ,长度为 2 .
例2:对于字符串 ")()())" , 来说, 最长的格式正确的子串是 "()()" ,长度为 4 .
字符串长度:
要求时间复杂度 ,空间复杂度
.
示例1
输入:
"(()"
复制返回值:
2
复制
示例2
输入:
"(())"
复制返回值:
4
题解
动态规划解法
- 使用dp[i]表示以下标i结尾时最长括号子串的长度
- 初始条件:dp[0] = 0,表示0个、1个字符时合法的括号子串的长度
- 状态转移方程:
- 如果dp[i]是),且dp[i-1]是(,那么dp[i] = dp[i-2] + 2
- 如果dp[i]是),且dp[i-1]是),那么需要去除掉dp[i-1]的合法长度之后,判断其前一位置是否为(,如果是则合法长度为dp[i] = dp[i - dp[i-1] - 1] + 2,如果不是(那么dp[i] = 0
代码如下:
int longestValidParentheses_dp(std::string &s)
{
int max_length = 0;
std::vector<int> dp(s.size(), 0);
for (int i = 1; i < s.size(); ++i)
{
if (s[i] == '(')// 不对左括号做统计
{
continue;
}
// s[i] == ')'
if (s[i - 1] == '(')// 前一字符为(,刚好和s[i]匹配,在dp[i-2]的长度上+2即可
{
dp[i] = dp[i - 2] + 2;
}
// s[i-1]为右括号,由于dp[i-1]记录了右括号匹配的长度,继续判断去处这个长度之后前面是否有匹配的左括号
// 比如(()()),对于i=5,dp[i-1]=4,i-dp[i-1]-1的值为0,因此判断的是s[0]是否为左括号
else if (i - dp[i - 1] > 0 && s[i - dp[i - 1] - 1] == '(')
{
// dp[i] = dp[i - 1] + 2 表示的是s[i-1]这个地方的右括号原来的长度+当前匹配的左右括号长度
// dp[i - dp[i - 1] - 2]则表示和s[i]匹配的左括号的左边一位的值
// 比如()(()()),总长度为8,当i等于7的时候
// i-1=6,dp[i-1]=4,dp[1]=2,dp[i-dp[i-1]-2] = dp[7-4-2] = dp[1] = 2
// dp[i] = dp[i-1] + 2 + dp[i - dp[i - 1] - 2] = dp[6] + 2 + dp[1] = 4 + 2 + 2 = 8
dp[i] = dp[i - 1] + 2 + (i - dp[i - 1] >= 2 ? dp[i - dp[i - 1] - 2] : 0);
}
max_length = std::max(max_length, dp[i]);
}
return max_length;
}
栈解法
贪婪解法
遍历字符串,并使用2个计数器分别统计左括号的个数,以及右括号的个数。我们知道当到达某一索引i的时候,如果括号子串是合法的,此时应该左括号个数大于等于右括号的个数,如果右括号个数大于左括号个数则到达当前位置时的子串是非法的。在左右括号个数相等的时候记录最大长度,当右括号个数大于左括号个数时清零计数器。
在正序遍历完字符串后,还需要逆序遍历一遍,因为对于(()这样的输入,从左到右的遍历会导致左括号数和右括号数永远无法匹配。
代码如下:
int longestValidParentheses(string s)
{
int left_count = 0;
int right_count = 0;
int max_count = 0;
for (int i = 0; i < s.size(); ++i)
{
if (s[i] == '(')
{
left_count++;
}
else
{
right_count++;
if (right_count == left_count)// 这里需要严格相等,因为(()这种是非法的,如果使用right<left,那么会将非法的情况统计进去
{
max_count = std::max(max_count, right_count * 2);
}
else if (right_count > left_count)
{
right_count = left_count = 0;
}
}
}
left_count = right_count = 0;
for (int i = s.size() - 1; i >= 0; --i)
{
if (s[i] == ')')
{
right_count++;
}
else
{
left_count++;
if (left_count == right_count)
{
max_count = std::max(max_count, right_count * 2);
}
else if (left_count > right_count)
{
right_count = left_count = 0;
}
}
}
return max_count;
}
一种看似正确的错误解法
// 错误case:"()(()",预期输出2,实际输出4
// 因为(()这样的输入应该是非法的,但是这种统计方法却认为是合法的
// 需要严格检查左右括号是否完全相等,而不是仅仅进行个数的统计,改变后就又变成了第三种贪婪解法了
int longestValidParentheses_wrong(string s)
{
int max_length = 0;
int left_count = 0;
int count = 0;
for (int i = 0; i < s.size(); ++i)
{
if (s[i] == '(')
{
left_count++;
continue;
}
if (left_count == 0)
{
count = 0;
continue;
}
left_count--;
count++;
if (count > max_length)
{
max_length = count;
}
}
return max_length * 2;
}