题目描述
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例
示例 1:
输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”
示例 2:
输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”
示例 3:
输入:s = “”
输出:0
解题思路
(1)将字符串转换为 char 型数组;
(2)新开辟栈空间,用来存储括号的下标,栈底元素设置为-1;
(3)遍历数组,若当前元素为 ')' 且栈中元素数量大于1 且栈顶元素对应的数据为 '(',则将栈顶元素出栈;否则,将当前元素的下标入栈;
(4)遍历栈,计算相邻栈中元素的差值,取最大的,即为最终结果
public class LongestValidParentheses {
public static int longestValidParentheses(String s) {
if (s.length() <= 1) {
return 0;
}
char[] charArray = s.toCharArray();
Stack<Integer> invalidParenthesesIndexStack = new Stack<Integer>();
invalidParenthesesIndexStack.push(-1);
for (int i = 0; i < charArray.length; i++) {
// 如果是 ')', 并且栈顶元素对应的数据为 '(', 并且栈中至少有两个元素, 则将栈顶元素出栈
if (charArray[i] == ')' && invalidParenthesesIndexStack.size() > 1 && charArray[invalidParenthesesIndexStack.peek()] == '(') {
invalidParenthesesIndexStack.pop();
} else {
invalidParenthesesIndexStack.push(i);
}
}
int result = 0;
if (invalidParenthesesIndexStack.peek() != s.length() - 1) {
result = s.length() - 1 - invalidParenthesesIndexStack.peek();
}
// 遍历栈
if (invalidParenthesesIndexStack.size() == 1) {
return s.length();
}
if (invalidParenthesesIndexStack.size() > 1) {
for (int i = 1; i < invalidParenthesesIndexStack.size(); i++) {
result = Math.max(result, invalidParenthesesIndexStack.get(i) - invalidParenthesesIndexStack.get(i - 1) - 1);
}
}
return result;
}
public static void main(String[] args) {
String s = "(()())()";
System.out.println("字符串为:" + s);
int result = longestValidParentheses(s);
System.out.println("最长有效括号为:" + result);
}
}
图解
以字符串 “(()())()” 为例:
1.将字符串转换为 char 型数组,并新创建栈,栈底元素初始化为 -1,栈中存储的是数组中元素的下标;
2.遍历数组 Array,处理栈 Stack 的谈栈与压栈
(1)i = 0 时,Array[i] == '(',直接进行压栈操作,此时栈 Stack 中有两个元素,-1 和 0;
(2)i = 1 时,Array[i] == '(',直接进行压栈操作,此时栈中有三个元素,-1、0 和 1;
(3)i = 2 时,Array[i] == ')',此时栈顶元素为 1,对应的数组中的元素为 '(',满足弹栈条件,所以将栈顶元素出栈,之后栈中元素为 -1 和 0。
(4)i = 3 时, Array[i] == '(',直接进行压栈操作,此时栈中有三个元素,-1、0 和 3;
(5)i = 4 时,Array[i] == ')',此时栈顶元素为 3,对应的数组中的元素为 '(',满足弹栈条件,所以将栈顶元素出栈,之后栈中元素为 -1 和 0。
(6)i = 5 时,Array[i] == ')',此时栈顶元素为 0,对应的数组中的元素为 '(',满足弹栈条件,所以将栈顶元素出栈,之后栈中元素为 -1。
(7)i = 6 时, Array[i] == '(',直接进行压栈操作,此时栈中有两个元素,-1 和 6;
(7)i = 7 时, Array[i] == ')',此时栈顶元素为 6,对应的数组中的元素为 '(',满足弹栈条件,所以将栈顶元素出栈,之后栈中元素为 -1。
此时,遍历结束,栈中元素为 -1。我们可以看到,除 -1 外,栈中存储的元素其实就是原字符串中非法括号的下标。
3.遍历栈。
此时栈中元素为 -1,即栈初始化时的栈底元素。那么我们就认为,字符串中的括号都是有效的,于是直接返回字符串长度即可。
我们已经知道,栈中存储的元素即为无效括号在原字符串中的下标,那么就可以知道原字符串中有效括号的范围。以字符串 ")()()(()" 来说,遍历完数组之后,栈中元素为:-1、0、5。那么有效括号的范围即为[1,4],除此之外,也需要考虑到一种特殊情况,即下标 5 之后的括号都是有效的,所以需要在代码中对此种情况进行特殊处理。
还有一个疑问点便是:为什么初始化栈时需要设置栈底元素为 -1?
简单说来就是为了计算方便。比如对于字符串 "()()()(()()" 来说,遍历完数组之后,栈中存储元素为 -1、6。最终得到的最长有效括号的长度为 Max(6, -1) - 1 = 6。如果不初始化栈底元素为 -1 的话,那么遍历完数组之后栈中只有一个元素 6,此时就需要去与 0 和原字符串长度做一系列的比较。当然,也不是非要初始化栈底元素为 -1,如果可以理清其中的关系的话,就没必要去初始化栈底元素了。
当然,最终在遍历栈的时候,可以在遍历之前将站定元素与字符串长度进行比较,如果栈顶元素不等于字符串长度 - 1,可以考虑先计算一下最终要返回的结果 result,也可以直接将字符串长度 - 1 的值进行压栈,使其参与遍历栈的操作。