给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
解法1:栈
类似于之前LeetCode20-有效的括号(该题的解法不再详述,大致思路:遇左括号入栈,遇右括号,先判断栈是否为空,若栈为空,则直接return false,若栈不为空,则出栈,继续处理下一字符;字符串结束时,若栈为空,return true,否则return false),可以用栈来解决。
但是做题的时候,发现了一个难点,即示例2的情况,第二个字符和第三个字符是一对有效的括号,此有效括号子串长度为2,第四个字符和第五个字符也是一对有效的括号,且这两个有效括号子串是挨着的,返回的结果应该是这两个有效括号子串长度之和。
用下面这个例子来看更直观一点:
下标3-14是一个有效括号子串,长度为12;15-16也是一个有效括号子串,长度为2;17-20是一个有效括号子串,长度为4,但最长有效括号子串长度并不是12,也不是12+2+4,而是下标2-21的有效括号子串,长度为20。
所以此种做法的难度在于如何判断两个有效括号子串是否直接相邻,若直接相邻,则最长有效括号子串需要将这两个相邻的子串包含进来,并且左右边界需往外扩展,如上例中,下标2和下标21所处位置的字符其实也是在最长有效括号子串中的。
解题思路:
1.需有一个变量start记录有效括号子串的起始下标,max表示最长有效括号子串长度,初始值均为0
2.遍历给字符串中的所有字符
2.1若当前字符s[index]为左括号'(',将当前字符下标index入栈(下标稍后有其他用处),处理下一字符
2.2若当前字符s[index]为右括号')',判断当前栈是否为空
2.2.1若栈为空,则start = index + 1,处理下一字符(当前字符右括号下标不入栈)
2.2.2若栈不为空,则出栈(由于仅左括号入栈,则出栈元素对应的字符一定为左括号,可与当前字符右括号配对),判断栈是否为空
2.2.2.1若栈为空,则max = max(max, index-start+1)
2.2.2.2若栈不为空,则max = max(max, index-栈顶元素值)
Java代码:
class Solution {
public int longestValidParentheses(String s) {
int max = 0, start = 0;
if(null == s) return 0;
int len = s.length();
Stack<Integer> stack = new Stack<>();
for(int index = 0; index < len; index++){
//遇左括号(,压栈(栈中元素为当前位置所处的下标)
if('(' == s.charAt(index)){
stack.push(index);
continue;
} else {
if(stack.isEmpty()){
start = index+1;
continue;
} else {
stack.pop();
if(stack.isEmpty()){
max = Math.max(max, index-start+1);
} else {
max = Math.max(max, index-stack.peek());
}
}
}
}
return max;
}
}
解法2:动态规划
需用到辅助数组d[s.length()],表示从当前字符开始,到字符串结尾的最长有效括号子串长度(当前字符需为有效括号子串的第一个字符)
解题思路:从字符串结尾往前处理,求辅助数组d[]
当前字符下标为index,若当前字符为左括号'(',判断index+1+d[index+1]位置的字符是否为右括号')',若为右括号,则d[index] = d[index+1]+2,并且判断index+1+d[index+1]+1位置的元素是否存在,若存在,则d[index] += d[index+1+d[index+1]+1](解决上述两个有效括号子串直接相邻的情况)
Java代码:
class Solution {
public int longestValidParentheses(String s) {
if(null == s) return 0;
int len = s.length(), max = 0;
int[] d = new int[len];
for(int index = len-2; index >= 0; index--){
int symIndex = index+1+d[index+1];
if('(' == s.charAt(index) && symIndex < len && ')' == s.charAt(symIndex)){
d[index] = d[index+1]+2;
if(symIndex+1 < len){
d[index] += d[symIndex+1];
}
}
max = Math.max(max, d[index]);
}
return max;
}
}
动态规划思想:将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
类似问题还有LeetCode5-最长回文子串,有一种解法是Mancher 算法,也用到了类似的思想。