题目地址:
https://leetcode.com/problems/longest-valid-parentheses/
给定一个括号序列 s s s,求其最长的合法括号子串的长度。
思路是用栈(此题还有一个比较好写的动态规划做法,参考https://blog.csdn.net/qq_46105170/article/details/108504436)。由于要计算合法的括号子串的长度,这里栈要存储下标而不是括号本身。
对于一个括号序列,从左到右遍历之。如果遇到左括号,就入栈;如果遇到右括号(下标是
j
j
j),此时如果栈不空,就将栈顶的左括号与之匹配(算法叙述完毕后将会知道右括号是不会入栈的,所以此处不考虑栈顶是右括号的情形),先将栈顶出栈,此时若栈非空且栈顶为
i
i
i,而
s
[
i
+
1
,
.
.
.
,
j
]
s[i+1,...,j]
s[i+1,...,j]是合法的括号序列,所以用
j
−
i
j-i
j−i更新最终答案;若栈空,此时栈并没有记录序列长度,所以要另外开一个变量start
来存储合法序列的开头位置,然后用长度更新答案。这个start
一开始设为
0
0
0,一旦遇到未匹配的右括号,就置为这个右括号的后一位即可。
算法正确性证明:对括号长度做归纳。当长度为
1
1
1或者
2
2
2的时候算法正确。考虑括号长度为
k
k
k的情况。如果遍历到的第一个括号是右括号,那这个括号不可能属于合法序列,此时start
被更新为
1
1
1,接下来就是对长度
k
−
1
k-1
k−1的序列进行算法了,据归纳假设,如果未用start
更新答案,则答案正确,若用到了start
,由于start
此时是
1
1
1,答案也是对的;如果遍历到的第一个括号是左括号,此时start
是
0
0
0,接下来就是在遍历长
k
−
1
k-1
k−1的序列,按照上面类似的推理也知道算法正确。
代码如下:
import java.util.ArrayDeque;
import java.util.Deque;
public class Solution {
public int longestValidParentheses(String s) {
if (s == null || s.isEmpty()) {
return 0;
}
int res = 0;
// 开一个栈存储括号的下标
Deque<Integer> stack = new ArrayDeque<>();
int start = 0;
for (int i = 0; i < s.length(); i++) {
// 左括号直接入栈
if (s.charAt(i) == '(') {
stack.push(i);
} else {
// 遇到右括号,如果栈空,说明该右括号要舍弃,不会成为合法序列的开头,将start置为i + 1
if (stack.isEmpty()) {
start = i + 1;
} else {
// 否则将栈内的左括号弹出来,这样栈顶到i这一段(不包括栈顶)的这一部分就是个合法的序列,更新res;
// 若栈空,则用start到i的这一段更新res
stack.pop();
if (stack.isEmpty()) {
res = Math.max(res, i - start + 1);
} else {
res = Math.max(res, i - stack.peek());
}
}
}
}
return res;
}
}
时空复杂度 O ( n ) O(n) O(n)。
也可以不用开start这个变量,而直接将无法匹配的右括号下标也入栈。代码如下:
import java.util.ArrayDeque;
import java.util.Deque;
public class Solution {
public int longestValidParentheses(String s) {
int res = 0;
Deque<Integer> stk = new ArrayDeque<>();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stk.push(i);
} else {
if (!stk.isEmpty() && s.charAt(stk.peek()) == '(') {
stk.pop();
res = Math.max(res, i - (stk.isEmpty() ? -1 : stk.peek()));
} else {
stk.push(i);
}
}
}
return res;
}
}
时空复杂度一样。