1. 最长有效括号问题解析
这道题目要求我们找出一个仅包含 '(' 和 ')' 的字符串中最长的有效括号子串的长度。有效括号子串需要满足每个左括号都有对应的右括号,并且顺序正确。例如:"()"、"(()())"都是有效的,而")("、"(()"则是无效的。
1.1 问题难点分析
这道题的难点在于:
- 需要处理括号的嵌套情况,如"(())"
- 需要处理并列的有效括号,如"()()"
- 需要从整个字符串中找出最长的连续有效子串,而不是判断整个字符串是否有效
- 时间复杂度要求控制在O(n)或O(n log n)级别
2. 栈解法详解
2.1 栈解法的核心思想
栈解法是最直观的解决方案,其核心思想是利用栈来记录括号的索引位置,通过索引差来计算有效子串的长度。与简单的括号匹配问题不同,这里我们需要计算的是最长有效子串的长度,而不是仅仅判断是否匹配。
栈中存储的是字符的下标而不是字符本身,这样当我们匹配成功时,可以通过下标差计算出长度。关键技巧是在栈中预置一个-1作为"参照物",用于计算从开头到当前位置的有效长度。
2.2 算法步骤详解
- 初始化一个空栈,并将-1压入栈中(作为起始参照物)
- 遍历字符串的每个字符:
- 如果当前字符是'(',将其下标压入栈
- 如果当前字符是')':
- 先弹出栈顶元素(可能是匹配的左括号或参照物)
- 如果栈为空,说明弹出的就是参照物,当前右括号无法匹配,将其下标压入栈作为新的参照物
- 如果栈不为空,说明成功匹配,计算当前有效长度=当前下标-栈顶元素下标,并更新最大值
2.3 代码实现与注释
java复制public int longestValidParentheses(String s) {
int maxLen = 0;
Stack<Integer> stack = new Stack<>();
stack.push(-1); // 初始参照物
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
stack.push(i); // 左括号入栈
} else {
stack.pop(); // 弹出栈顶元素
if (stack.isEmpty()) {
stack.push(i); // 栈空,当前右括号作为新参照物
} else {
// 计算当前有效长度并更新最大值
maxLen = Math.max(maxLen, i - stack.peek());
}
}
}
return maxLen;
}
2.4 复杂度分析
- 时间复杂度:O(n),每个字符最多入栈和出栈一次
- 空间复杂度:O(n),最坏情况下需要存储所有字符的索引
3. 动态规划解法详解
3.1 动态规划思路
动态规划是解决这类问题的更通用方法。我们定义dp[i]表示以第i个字符结尾的最长有效括号子串的长度。注意:
- 如果s[i] == '(',那么以它结尾的子串不可能是有效的,所以dp[i] = 0
- 我们只需要在s[i] == ')'时更新dp[i]
3.2 状态转移方程
分两种情况讨论:
-
情况一:s[i-1] == '('(形如"...()")
- dp[i] = dp[i-2] + 2
-
情况二:s[i-1] == ')'(形如"...))")
- 如果s[i - dp[i-1] - 1] == '(',则:
dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2]
- 如果s[i - dp[i-1] - 1] == '(',则:
3.3 代码实现
java复制public int longestValidParentheses(String s) {
int maxLen = 0;
int[] dp = new int[s.length()];
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == ')') {
if (s.charAt(i - 1) == '(') {
// 情况一:...()
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
} else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
// 情况二:...)) 且找到匹配的左括号
dp[i] = dp[i - 1] + 2 + (i - dp[i - 1] >= 2 ? dp[i - dp[i - 1] - 2] : 0);
}
maxLen = Math.max(maxLen, dp[i]);
}
// 左括号时dp[i] = 0,无需处理
}
return maxLen;
}
3.4 复杂度分析
- 时间复杂度:O(n),只需一次遍历
- 空间复杂度:O(n),用于存储dp数组
4. 双指针计数法(空间优化)
4.1 算法思想
双指针计数法可以在O(1)空间内解决这个问题。基本思路是用两个计数器left和right分别统计当前遍历到的左括号和右括号数量:
- 当left == right时,说明找到一段有效括号,长度为left + right,更新最大值
- 当right > left时,说明这段已经无效,重置计数器
但这种方法有一个缺陷:当左括号数量始终大于右括号时,永远无法触发left == right的条件。例如"(((()"。解决方案是从右向左再遍历一次,交换左右括号的判断逻辑。
4.2 算法步骤
-
从左到右遍历:
- 遇到'('则left++
- 遇到')'则right++
- 如果left == right,更新最大值
- 如果right > left,重置left和right为0
-
从右到左遍历:
- 遇到')'则right++
- 遇到'('则left++
- 如果left == right,更新最大值
- 如果left > right,重置计数器
4.3 代码实现
java复制public int longestValidParentheses(String s) {
int maxLen = 0;
int left = 0, right = 0;
// 从左到右遍历
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
left++;
} else {
right++;
}
if (left == right) {
maxLen = Math.max(maxLen, left + right);
} else if (right > left) {
left = 0;
right = 0;
}
}
// 从右到左遍历
left = 0;
right = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == ')') {
right++;
} else {
left++;
}
if (left == right) {
maxLen = Math.max(maxLen, left + right);
} else if (left > right) {
left = 0;
right = 0;
}
}
return maxLen;
}
4.4 复杂度分析
- 时间复杂度:O(n),需要两次遍历
- 空间复杂度:O(1),只使用了几个变量
5. 三种解法对比与选择
5.1 性能对比
| 维度 | 栈解法 | 动态规划 | 双指针计数法 |
|---|---|---|---|
| 时间复杂度 | O(n) | O(n) | O(n) |
| 空间复杂度 | O(n) | O(n) | O(1) |
| 代码复杂度 | 简单 | 中等 | 简单 |
| 理解难度 | 直观 | 需要推导 | 需理解双向遍历 |
5.2 面试建议
- 优先掌握动态规划解法,因为它最能体现算法思维
- 可以先讲栈解法作为过渡,然后引出动态规划
- 双指针法可以作为加分项,展示优化能力
6. 常见问题与解答
6.1 为什么动态规划中只需要考虑s[i] == ')'的情况?
因为有效括号子串不可能以左括号结尾。如果以左括号结尾,这个子串一定是不完整的,所以dp[i] = 0无需处理。
6.2 栈解法中为什么要预置-1?
-1是一个虚拟的起始点,用于计算从字符串开头开始的有效长度。当第一个字符是'('时,-1的存在让我们可以计算出1 - (-1) = 2这样的长度。如果第一个字符是')',-1会被弹出,然后当前下标入栈成为新的参照物。
6.3 双指针法为什么要双向遍历?
单向遍历无法处理左括号数量始终大于右括号的情况,例如"(((()"。从右到左遍历时,交换判断逻辑,就能正确处理这种场景。
7. 实际应用场景
- 代码编辑器的括号高亮:识别代码中的括号匹配情况
- 表达式求值中的括号检查:确保括号的正确嵌套
- 文本编辑器的自动补全:预测用户意图并自动补全括号
- 编译器的语法分析:检查括号匹配,定位语法错误
- 数据压缩中的模式识别:识别重复的括号模式进行压缩
8. 扩展思考
-
如果字符串中不仅有括号,还有字母和数字(需要忽略它们只验证括号),代码怎么改?
- 可以在遍历时跳过非括号字符,只处理'('和')'
-
如何用栈解法返回最长有效括号子串本身(而不是长度)?
- 可以记录最长子串的起始和结束索引,最后用substring提取
-
如何用这道题的思路解LeetCode 20(有效的括号)和LeetCode 22(括号生成)?
- 20题可以看作是最长有效括号的特殊情况
- 22题可以使用类似的匹配思想来生成所有可能的有效组合