32. Longest Valid Parentheses

"""
32. Longest Valid Parentheses
Hard
1343
68


Given a string containing just the characters '(' and ')', find the length of the longest valid (well-formed) parentheses substring.

Example 1:

Input: "(()"
Output: 2
Explanation: The longest valid parentheses substring is "()"
Example 2:

Input: ")()())"
Output: 4
Explanation: The longest valid parentheses substring is "()()"
"""

    今天写leetcode的第32题,题目的大概意思是一个字符串s,是由"(",")"两种字符组成的,问其中最长的左右配对的子字符串是多长。诸如()(()),(()()),这样的都是匹配的。

    这道题目想了很久才做出来。

    从左到右遍历,用一个栈preleftlist来记载"("的数量,用另一个栈tmplist记载连续子字符串的数量,为什么要用两个栈呢,用maxlen记载最长子串长度,用tmp从左到右遍历考虑到这样的情况"()(()"这个的最大子串长度是2,但是如果在最右边再加上一个")"的话就会变成"()(())"这样的话就是6个,也就是最前面的2个可能还有用,所以要用栈来存储数量的信息,具体操作是这样子的从右向左遍历,preleft来记载连续的"("的数量,将preleft进栈到preleftlist栈中,继续遍历,接下来是连续的")",每当有一个")"那么栈preleftlist的顶端的数字就-1,同时tmp+=2;一直如此,直到遇见"(",就将此时的tmp进栈到tmplist中,同时更新maxlen;在此过程中遇见栈preleftlist顶端的数字变成0的话,preleftlist出栈,同时tmplist如果存在栈首就出栈加到tmp上面,这就相当于将原先被"("隔离的两部分加在了一起;如果遇见preleftlist为空的情况的话,说明这个")"找不到可以配对的"("的,此时更新maxlen后就将所有栈清空;代码如下:

class Solution:
    def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        s_len = len(s)
        preleftlist = []
        preleft = 0
        maxlen = 0
        tmp = 0
        tmplist = []
        i = 0
        while i < s_len:
            while i < s_len and s[i] == "(":
                preleft += 1
                i += 1
            if preleft:
                preleftlist.append(preleft)
                preleft = 0
            while i < s_len and s[i] == ")":
                if preleftlist:
                    tmp += 2
                    preleftlist[-1] -= 1
                    #这一层的左括号已经用完了
                    if not preleftlist[-1]:
                        preleftlist.pop()
                        #如果前面成对的()就把数量合并
                        if tmplist:
                            tmp += tmplist[-1]
                            tmplist.pop()
                #这个else说明右括号已经找不到可以匹配的左括号了
                else:
                    maxlen = max(tmp, maxlen)
                    tmp = 0
                    tmplist = []
                i +=1
            #之前的循环中右括号找到的tmp计算进去
            if tmp:
                tmplist.append(tmp)
                maxlen = max(tmp, maxlen)
            tmp = 0
        return max(tmp, maxlen)

    在评论区看到一个很有意思的c++的空间复杂度是O(1)的算法:

    int longestValidParentheses(string s) {
        calcInvalid(s, '(');
        reverse(s.begin(), s.end());
        return calcInvalid(s, ')');
    }
    
    int calcInvalid(string& s, char plus) {
        int stack_ = 0, invalid = 0, longest = 0, length = 0;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == plus) {
                stack_++;
            } else {  
                if (s[i] == '#' || stack_ == 0) {
                    s[i] = '#';
                    stack_ = 0;
                    length = 0;
                    continue;
                }
                stack_--;
            }
            
            longest = max(longest, ++length);
        }
        return longest;
    }

    看了一会才看懂是什么意思,对于字符串s,首先考虑到这几点:

1,每一个右括号要么不配对要么只有唯一的"("与之相对应;

2,从前遍历,和从后遍历本无本质区别

3,每一个"("要么不配对,要么只有唯一的")"与之相对应;

这个算法,我把它分成3段:

1, 基于这样的想法,首先从前向后遍历,有preleft记录"("的数量,遍历到右括号如果preleft>0就将preleft-1,同时说明这个右括号是有配对的,如果preleft==0说明这个右括号是不配对的,将这个右括号改成其他的某个字符,这样就可以找到所有不配对的")";

2, 再从后先前遍历找到所有不匹配的"(",再次改为其他字符,这样剩下的字符都是遍历的了

3,找到字符串中连续的只包含"("或者")"的最长子字符串的长度

步骤1和步骤2就只是从前遍历和从后遍历的区别可以写成一个函数,3完全可以在步骤2中进行统计,这就是为什么c++只有一个函数的原因

可惜的是因为python中str是不可变的,只能先转化为list所以空间复制度就不是O(1)了,下面是我写的python代码:

class Solution:
    def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        s_len = len(s)
        maxlen = 0
        tmp = 0
        preleft = 0
        lastright = 0
        s = list(s)
        for i in range(s_len):
            if s[i] == "(":
                preleft += 1
            else:
                if preleft:
                    preleft -= 1
                else:
                    s[i] = "@"
                    preleft = 0
        # for i in range(s_len-1,-1,-1):
        #     if s[i] == ")" or s[i] == "@" :
        #         lastright += 1
        #     else:
        #         if lastright:
        #             lastright -= 1
        #         else:
        #             s[i] = "@"
        #             lastright = 0
        for i in range(s_len-1,-1,-1):
            if s[i] == ")":
                lastright += 1
            elif(s[i]=="@"):
                lastright = 0
            else:
                if lastright:
                    lastright -= 1
                else:
                    s[i] = "@"
                    lastright = 0
        for i in range(s_len):
            if s[i] != "@":
                tmp += 1
            else:
                maxlen = max(maxlen, tmp)
                tmp = 0
        return max(maxlen, tmp)

注释部分的代码和下面的是可以互换的,都是步骤2的实现

听说还可以用动态规划做,我等等试着写一下

原文地址:https://www.cnblogs.com/mangmangbiluo/p/10091439.html