【leetcode】最长有效括号(线性dp,栈)

给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。

示例 1:

输入: “(()”
输出: 2
解释: 最长有效括号子串为 “()”
示例 2:

输入: “)()())”
输出: 4
解释: 最长有效括号子串为 “()()”

链接:https://leetcode-cn.com/problems/longest-valid-parentheses

方法一:动态规划 O(n)

有效括号最小有两种情况:

  1. () 左括号和右括号相邻。
  2. (()) 左括号和右括号不相邻。

基于这两种基本情况设计dp. 设dp[i]是以s[i]字符结尾的最长有效括号长度。那么可以分析出每个’(‘对应下标的长度都是0. 对于’)’,按照上面两种情况来设计方程。
对于第一种情况,若s[i] == ‘)’ && s[i-1] == ‘(’ 那么dp[i] = dp[i-2]+2.
为什么这样设计呢?因为如果s[i-2]是左括号和无效的右括号(多出来的),那么dp[i]=2毋庸置疑。如果s[i-2]是一个有效的右括号,那么最长有效括号长度就是之前的有效括号长度(dp[i-2])加上当前’()'的括号长度2。
对于第二种情况,若s[i] == ‘)’ && s[i-1] == ‘)’ && s[i-dp[i-1]-1] == ‘(’
则dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2].

令pre_index = i-dp[i-1]-1 先分析一下pre_index的含义:dp[i-1]表示前面右括号的最长有效括号长度,那么i-dp[i-1]-1就是指向当前右括号应当匹配的左括号的下标。
举个例子:)(()) 对于最后的右括号来说,i = 4,dp[i-1] = 2,pre_index = i-dp[i-1]-1 = 1,i=4的右括号所匹配的左括号的位置应当在下标1的位置。于是对这个位置即pre_index判断:s[pre_index]是右括号,不配对,dp[i] = 0;如果s[pre_index]是左括号,配对成功,dp[i] = dp[i-1]+2. 至于加上的dp[i-dp[i-1]-2],即dp[pre_index-1]的含义其实和第一种情况的dp[i-2]的含义是一样的,如果s[pre_index-1]也是一个有效右括号,那么就要将之前的有效括号长度连起来。比如()(()),对于最后的右括号i = 5, pre_index-1=1,dp[1] = 2,两个长度连起来,dp[i] = 2+4 = 6.
代码如下:

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

方法二:栈

其实对于这种有效括号的问题,最先想到的应该就是栈。不过题目要求的是最长有效括号,用栈去做的时候发现有一些断点不知道怎么处理,看了leetcode题解才发现这种栈的运用之巧妙。
举一个例子:()((())和())(()) 一个是s[2]=’(’ ,一个是s[2]=’)’. 这个s[2]就是一个断点。如果我们按照正常的括号匹配来做,就是遇到左括号进栈,遇到右括号且栈不空,将左括号出栈,这样就无法处理断点的问题。
我们可以换个方式运用栈:设栈名为st,将左括号和断点转化为下标进栈。先将-1压进栈作为起点便于计算区间长度。
拿上面的第二个例子说明:
i=0,左括号进栈。
i=1,遇到右括号,左括号出栈,计算长度:i-st.top() = 1-(-1) = 2.
i=2,遇到右括号,-1出栈。这时候栈空了,右括号进栈作为断点。
i=3,左括号进栈。
i=4,左括号进栈。
i=5,遇到右括号,左括号出栈,计算长度:5-st.top() = 5-3 = 2.
i=6,遇到右括号,左括号出栈,这时候栈中剩下的就是之前进栈的右括号断点。计算长度:6-st.top() = 6-2 = 4.
因此,我们就可以得到最大的长度为4. 如果栈空了,就表示右括号多出来了,前面和后面不能连起来,因此该右括号下标进栈表示断点就可以方便计算后面的有效括号长度,然后比较前面和后面取最大就是最长有效括号。
代码如下:

class Solution {
public:
    int longestValidParentheses(string s) {
        int maxans = 0;
        stack<int> stack;
        stack.push(-1);
        for (int i = 0; i < s.length(); i++) 
        {
            if (s[i] == '(')    //遇到左括号,压栈
                stack.push(i);
            else    //遇到右括号,出栈。若栈空,右括号进栈;若栈不空,计算长度。
            {
                stack.pop();    //设定断点
                if (stack.empty())
                    stack.push(i); 
                else 
                    maxans = max(maxans, i - stack.top());
            }
        }
        return maxans;
    }
};

你可能感兴趣的:(leetcode,算法,动态规划)