题目分析

题目链接:32. Longest Valid Parentheses

输入的字符串s由'('和')'组成,其中存在一些子串是合法的括号字符串。
比如(()())是合法的括号字符串;()())(())就不是合法的括号字符串,但是其中的()()(())是合法的括号字符串。

原题目要求找到最长合法括号子串的长度,但是我们提出进一步的要求:找出其中的所有合法括号子串。找出了所有合法括号子串,最长子串的长度自然也能得到。

题解1:利用栈找出所有合法括号子串

表示嵌套关系的最好方式就是栈。比如函数调用,比如括号匹配。
合法括号字符串的一个充分必要条件是:将其中字符压入栈中,如果压入)时栈顶恰好是(,那么将这两个字符都出栈(它们组成一个括号),压入所有字符以后栈是空的

因此我们可以将输入字符串s的所有字符依次按照这种方式压入栈中,其中构成括号的子串会自己抵消,最终留在栈中的字符是所有没有被匹配的)(在s中,在这些没有被匹配的字符之间,就是合法的括号字符串。

代码实现

class Solution
{
  public:
    int longestValidParentheses(string s)
    {
        stack<int> sta;
        for (int i = 0; i < s.size(); i++)
        {
            // 将输入字符依次压入栈中
            if (s[i] == '(')
            {
                sta.push(i);
            }
            else if (s[i] == ')')
            {
                if (sta.size() > 0)
                {
                    if (s[sta.top()] == '(')
                    {
                        // 相匹配的)和(相互抵消,最后栈中只剩下没有匹配的)和(
                        sta.pop();
                    }
                    else
                    {
                        sta.push(i);
                    }
                }
                else
                {
                    // 如果)是栈中第一个元素,那么它必定没有匹配
                    sta.push(i);
                }
            }
        }
        int start, end = s.size(), length = 0, max_length = 0;
        while (sta.size() > 0)
        {
            start = sta.top();
            sta.pop();
            // 在s中,在没有匹配的)和(之间,就是那些被匹配抵消的符号
            length = end - start - 1;
            if (length > max_length)
                max_length = length;
            end = start;
        }
        // 栈中最后剩余的符号之前,也是合法的括号子串
        length = end;
        if (length > max_length)
            max_length = length;
        return max_length;
    }
};

时间复杂度

该算法先扫描一遍s再扫描一遍栈中的剩余元素,每一次针对扫描的值仅仅做O(1)的简单计算,因此总的时间复杂度为O(n)。


题解2:动态规划,利用s[i]之前的匹配情况帮助计算s[i]的匹配情况。

使用一个数组longest,longest[i]存储以s[i]结尾的最长括号子串的长度。比如对于s:()())(()),longest为[0,2,0,4,0,0,0,2,4]。
只要算出了longest,我们就能得到每个括号子串。如果longest[k]==n那么就存在一个合法括号子串:s[k-n+1]~s[k]。

那么如何计算longest呢?我们利用动态规划的思想:从longest[0]开始,利用s[0]~s[i-1]和longest[0]~longest[i-1]的值,来计算longest[i]的值:

  • 如果s[i]==(,那么longest[i] = 0。因为以(结尾的必定不是合法的括号字符串。
  • 如果s[i]==)

    • 如果s[i-1]==(,那么longest[i] = longest[i-2] + 2。
    • 如果s[i-1]==)

      • 如果s[i-longest[i-1]-1]==(,那么longest[i] = longest[i-1] + 2 + longest[i-longest[i-1]-2]。
      • 否则longest[i] = 0。

代码实现

class Solution
{
  public:
    int longestValidParentheses(string s)
    {
        int size = (int)s.size(), current_max = 0;
        vector<int> longest(size, 0);
        for (int i = 1; i < size; i++)
        {
            if (s[i] == '(')
            {
                longest[i] = 0;
            }
            else
            {
                // s[i]==')'
                if (s[i - 1] == '(')
                {
                    longest[i] = i - 2 >= 0 ? (longest[i - 2] + 2) : 2;
                    current_max = longest[i] > current_max ? longest[i] : current_max;
                }
                else
                {
                    // s[i-1]==')'
                    if (i - longest[i - 1] - 1 >= 0 && s[i - longest[i - 1] - 1] == '(')
                    {
                        longest[i] = (i - longest[i - 1] - 2 >= 0) ? (longest[i - 1] + 2 + longest[i - longest[i - 1] - 2]) : (longest[i - 1] + 2);
                        current_max = longest[i] > current_max ? longest[i] : current_max;
                    }
                    else
                    {
                        longest[i] = 0;
                    }
                }
            }
        }
        return current_max;
    }
};

时间复杂度

该算法仅仅对每个字符做一次O(1)的简单计算,因此时间复杂度也是O(n)。


csRyan
1.1k 声望198 粉丝

So you're passionate? How passionate? What actions does your passion lead you to do? If the heart doesn't find a perfect rhyme with the head, then your passion means nothing.