最长匹配括号子序列问题

题目

给定一个字符串只包含’(‘和’)’,输出其中最长的括号正确匹配的子序列的长度。

举例

输入 输出
()) 2
)()()) 4
(() 2
(()()()()( 8

以下将介绍三种复杂度为O(n)的算法

算法1

该算法利用了栈来遍历可能的匹配的括号子序列,遍历的同时找出最长的子序列。遍历不会遍历所有的匹配括号子序列,并排的一些括号只有包含最左边括号的子序列被遍历,最长的括号子序列肯定能被遍历。
栈顶记录的是与目前探查的括号序列并列的最左边的括号的前一个字符的位置。
从左向右遍历字符串元素。遇到左括号则将该位置压入栈。如果遇到右括号的话,如果不能与栈顶的左括号匹配,则说明这个右括号是多余的,压入栈。如果遇到右括号能匹配,则找到了一个括号序列。这个时候栈顶元素为与这个右括号匹配的左括号的位置。但注意到可能有多个括号并列的情况,进行一次出栈后栈顶元素就是与目前探查的括号序列并列的最左边的括号的前一个字符的位置。观察到了一个新的括号序列,更新最大长度。

int longestValidParentheses1(std::string s) {
    std::stack<int> beginOfPossibleParenStack;
    int result = 0;
    beginOfPossibleParenStack.push(-1);
    for (int i = 0; i < s.size(); ++i) {
        if (s[i] == ')' &&
            beginOfPossibleParenStack.size() > 1 &&
            s[beginOfPossibleParenStack.top()] == '(')
        {
            beginOfPossibleParenStack.pop();
            result = std::max(result, i - beginOfPossibleParenStack.top());
        } else {
            beginOfPossibleParenStack.push(i);
        }
    }
    return result;
}

算法2

算法2使用了动态规划思想。使用一个数组记录以i结尾的最大匹配括号子序列长度。
还是从左向右遍历字符串,同时记录左括号减去右括号的个数,最小为0,借此可以忽略多余的右括号。找到了一个不多余的右括号后,计算以他结尾的括号序列长度,不仅包括这对括号内部的括号,还要加上这对括号前面的与其并列的括号。

int longestValidParentheses2(std::string s) {
    std::vector<int> lenOfLongestParenEndedAt = std::vector<int>(s.size(), 0);
    int result = 0;
    int leftParenthesesCount = 0;
    for (int i = 0; i < s.size(); ++i) {
        if (s[i] == '(') {
            leftParenthesesCount++;
        } else if (leftParenthesesCount > 0) {
            leftParenthesesCount--;
            lenOfLongestParenEndedAt[i] = lenOfLongestParenEndedAt[i - 1] + 2;
            int beforeCurrentParen = i - lenOfLongestParenEndedAt[i];
            if (beforeCurrentParen >= 0) {
                lenOfLongestParenEndedAt[i] += lenOfLongestParenEndedAt[beforeCurrentParen];
            }
            result = std::max(result, lenOfLongestParenEndedAt[i]);
        }
    }
    return result;
}

算法3

这个算法不仅时间复杂度为O(n),而且空间复杂度为O(1)。
原字符串中匹配序列左侧要么是空要么是括号不匹配的,右侧也一样。字符串相当于一个已经匹配的括号序列左边和右边加入不匹配的括号序列产生的。
对于一个括号完全匹配的字符串,在从左向右遍历的同时记录左括号和右括号的个数,那么显然遇到左括号个数等于右括号个数的时候则已经遍历的部分是括号完全匹配的,匹配括号序列中左括号数目等于右括号数目,这样就可以知道匹配括号子序列的长度。在这个的基础上,如果在字符串的右边加入一个括号不匹配的序列,则原算法可以继续工作。如果在字符串左侧加入一个右括号比左括号多的序列,则原来的算法无法工作,但是只要发现右括号比左括号多则将已遍历的部分去掉,即将计数清零,算法就可以继续工作。如果在字符串左侧加入一个左括号比右括号多的序列,则原来的算法无法工作。这个时候只要从右向左再进行一遍上述算法,就能正常工作。

int longestValidParentheses3(std::string s) {
    int leftNum = 0;
    int rightNum = 0;
    int result = 0;
    for (int i = 0; i < s.length(); i++) {
        if (s[i] == '(') {
            leftNum++;
        } else {
            rightNum++;
        }
        if (leftNum == rightNum) {
            result = std::max(result, 2 * rightNum);
        } else if (rightNum > leftNum) {
            leftNum = 0;
            rightNum = 0;
        }
    }
    leftNum = rightNum = 0;
    for (int i = s.length() - 1; i >= 0; i--) {
        if (s[i] == '(') {
            leftNum++;
        } else {
            rightNum++;
        }
        if (leftNum == rightNum) {
            result = std::max(result, 2 * leftNum);
        } else if (leftNum > rightNum) {
            leftNum = 0;
            rightNum = 0;
        }
    }
    return result;
}

你可能感兴趣的:(最长匹配括号子序列问题)