我的LeetCode代码仓:https://github.com/617076674/LeetCode
原题链接:https://leetcode-cn.com/problems/longest-valid-parentheses/description/
题目描述:
知识点:栈、动态规划
思路一:利用栈的特性求解
这个思路和LeetCode020——有效的括号的思路很像,程序实现过程如下:
(1)设立一个int型变量result保存结果,初始化为0,begin保存所考虑的有效括号的子串的起始字符在字符串s中的位置,从0开始。
(2)遍历字符串s中的每一个字符,
a.如果当前字符为左括号——'(',直接入栈即可。
b.如果当前字符为右括号——')',
b-1:如果此时栈为空,说明这已经不是一个有效括号的子串,我们令begin = i + 1,即从把下一个位置当成是起始位置。
b-2:如果此时栈不为空,弹出栈顶元素,
b-2-1:如果此时栈为空,则说明此时的长度为i - begin + 1,result取result和该值中的较大者。
b-2-2:如果此时栈不为空,说明此时的长度为i - stack.peek(),result取result和该值中的较大者。
整个过程我们只遍历了一次字符串s,因此时间复杂度是O(n),其中n为字符串s的长度。在解题中我们用到了辅助工具——栈,因此空间复杂度是O(n)。
JAVA代码:
public class Solution {
public int longestValidParentheses(String s) {
int result = 0;
int begin = 0;
Stack<Integer> stack = new Stack<>();
for(int i = 0; i < s.length(); i++) {
if(s.charAt(i) == '(') {
stack.push(i);
}else {
if(stack.isEmpty()) {
begin = i + 1;
}else {
stack.pop();
if(stack.isEmpty()) {
result = Math.max(result, i - begin + 1);
}else {
result = Math.max(result, i - stack.peek());
}
}
}
}
return result;
}
}
LeetCode解题报告:
思路二:动态规划
从思路一的LeetCode解题报告中我们就可以看出来,思路一的解法不是最优的,此题还可以利用动态规划求解。动态规划的关键是找到合适的状态定义并发现正确的状态转移方程。
状态定义:
f(x)表示以字符串s中第x个字符结尾的有效括号的最长子串的长度。
状态转移:
(1)如果s.charAt(x) == '(',显然有效括号是不可能以'('结尾的,因此f(x) = 0。
(2)如果s.charAt(x) == ')',
a.如果s.charAt(x - 1) == '(',说明第x - 1个字符'('和第x个字符')'相匹配,因此f(x) = 2 + f(x - 2)。
b.如果s.charAt(x - 1) == ')',那么第x个字符')'该和谁去匹配呢?
注意,此时f(x - 1)表示以字符串s中第x - 1个字符结尾的有效括号的子串的最长长度,因此第x个字符')'应该和第x - f(x - 1) - 1个字符去配对。如果s.charAt(x - f(x - 1) - 1) == '(',那么f(x) = f(x - 1) + 2 + f(x - f(x - 1) - 2) ,其中f(x - f(x - 1) - 2)代表的是以字符串s中第x - f(x - 1) - 2个字符结尾的有效括号的最长子串的长度。
动态规划的本质是避免对重叠子问题的重复求解,那么思路二减少了哪些重叠子问题的求解呢?
在思路一中,我们利用了一个栈的额外的辅助空间,寻求有效括号的首字符在字符串s中的起始位置begin其实也是跳跃的,时间复杂度如前所述,是O(n)级别的。
在思路二中,我们减少了对于重叠子问题的重复求解,并不是相对思路一而言的,而是相对于我们的暴力解法而言的。
举个例子,对于字符串"(())"而言,如果使用暴力解法,我们在求第一个字符为起始字符所对应的有效括号的最长长度时,会求解第二个字符为起始字符所对应的有效括号的最长长度,而在求第二个字符为起始字符所对应的有效括号的最长长度时,我们就又重复计算了这一过程。而我们的动态规划就是先记录下了第二个字符为起始字符所对应的有效括号的最长长度,避免二次求解。
这一思路的时间复杂度是O(n)级别的,其中n为字符串s的长度。空间复杂度也是O(n)级别的,因为我们使用了一个大小为n的数组。之所以比思路一在LeetCode中解题要快,主要是因为在Java中入栈出栈操作损耗了比较大的时间。
JAVA代码:
public class Solution {
public int longestValidParentheses(String s) {
int result = 0;
int n = s.length();
int[] lens = new int[n];
for (int i = 0; i < n; i++) {
if(s.charAt(i) == ')') {
if(i >= 1) {
if(s.charAt(i - 1) == '(') {
if(i >= 2) {
lens[i] = lens[i - 2] + 2;
}else {
lens[i] = 2;
}
}else {
if(i - 1 - lens[i - 1] >= 0 && s.charAt(i - 1 - lens[i - 1]) == '(') {
if(i - 2 - lens[i - 1] >= 0) {
lens[i] = lens[i - 1] + lens[i - 2 - lens[i - 1]] + 2;
}else {
lens[i] = lens[i - 1] + 2;
}
}
}
}
}
result = Math.max(result, lens[i]);
}
return result;
}
}
LeetCode解题报告: