1. 题目解析与核心思路
Longest Valid Parentheses(最长有效括号)是LeetCode上的一道经典动态规划/栈应用题目,编号32,难度等级为Hard。题目要求在一个仅包含'('和')'的字符串中,找出最长的有效括号子串的长度。有效括号子串需要满足所有括号都能正确匹配闭合。
1.1 问题定义与示例分析
有效括号子串的判定标准:
- 每个左括号'('必须有对应的右括号')'匹配
- 匹配的括号对必须正确嵌套,不能交叉
示例分析: 输入:")()())" 输出:4 解释:最长有效子串是"()()"
1.2 暴力解法与复杂度分析
最直观的解法是检查所有可能的子串是否有效:
- 生成所有可能的子串(O(n^2)个子串)
- 对每个子串检查有效性(O(n)时间)
- 记录最大有效长度
时间复杂度:O(n^3) —— 对于n=3×10^4的字符串完全不可行
2. 动态规划解法详解
2.1 DP状态定义
定义dp[i]表示以s[i]结尾的最长有效括号子串长度。关键观察点:
- 只有')'能形成有效子串的结尾
- 对于s[i]=')',需要考察前一个字符s[i-1]
2.2 状态转移方程
情况1:s[i-1] = '('
dp[i] = dp[i-2] + 2 // 直接形成一对新括号情况2:s[i-1] = ')'
if s[i - dp[i-1] - 1] == '(': dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2]2.3 完整DP实现代码
def longestValidParentheses(s: str) -> int: n = len(s) if n == 0: return 0 dp = [0] * n max_len = 0 for i in range(1, n): if s[i] == ')': if s[i-1] == '(': dp[i] = (dp[i-2] if i >= 2 else 0) + 2 else: if i - dp[i-1] > 0 and s[i - dp[i-1] - 1] == '(': dp[i] = dp[i-1] + 2 + (dp[i - dp[i-1] - 2] if (i - dp[i-1]) >= 2 else 0) max_len = max(max_len, dp[i]) return max_len2.4 DP解法复杂度分析
时间复杂度:O(n) —— 单次遍历字符串 空间复杂度:O(n) —— 需要维护dp数组
3. 栈解法详解
3.1 栈解法核心思想
利用栈存储索引而不是字符,保持栈底元素为"最后一个未匹配的右括号索引":
- 初始时栈中压入-1作为哨兵
- 遇到'('压入当前索引
- 遇到')'弹出栈顶,计算当前索引与栈顶的差值
3.2 栈解法实现代码
def longestValidParentheses(s: str) -> int: stack = [-1] max_len = 0 for i in range(len(s)): if s[i] == '(': stack.append(i) else: stack.pop() if not stack: stack.append(i) else: max_len = max(max_len, i - stack[-1]) return max_len3.3 栈解法复杂度分析
时间复杂度:O(n) —— 单次遍历 空间复杂度:O(n) —— 最坏情况下栈大小与字符串长度相同
4. 最优解:双指针法
4.1 双指针法核心思路
通过左右两次遍历,统计有效括号长度:
- 从左到右遍历:统计左右括号计数
- 当left == right时更新max_len
- 当right > left时重置计数
- 从右到左遍历:处理左括号多于右括号的情况
4.2 双指针法实现代码
def longestValidParentheses(s: str) -> int: left = right = max_len = 0 n = len(s) # 从左到右扫描 for i in range(n): if s[i] == '(': left += 1 else: right += 1 if left == right: max_len = max(max_len, 2 * right) elif right > left: left = right = 0 left = right = 0 # 从右到左扫描 for i in range(n-1, -1, -1): if s[i] == '(': left += 1 else: right += 1 if left == right: max_len = max(max_len, 2 * left) elif left > right: left = right = 0 return max_len4.3 复杂度分析
时间复杂度:O(n) —— 两次遍历 空间复杂度:O(1) —— 仅使用常数空间
5. 解法对比与选择建议
| 解法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 动态规划 | O(n) | O(n) | 需要详细状态记录时 |
| 栈 | O(n) | O(n) | 需要处理嵌套结构时 |
| 双指针 | O(n) | O(1) | 内存受限时优先选择 |
实际面试中推荐使用栈解法,因为:
- 代码简洁直观
- 容易解释思路
- 处理复杂括号问题时扩展性强
6. 常见错误与调试技巧
6.1 边界条件处理
常见错误:
- 忘记处理空字符串情况
- 索引越界(如dp[i-2]当i=1时)
- 哨兵值设置不当(栈解法中初始-1)
调试方法:
- 用简单测试用例验证(如"()", ")(", "()()")
- 打印中间状态(dp数组或栈内容)
6.2 特殊测试用例
需要特别注意的测试用例:
- "" → 0
- "(()" → 2
- ")()())" → 4
- "()(()" → 2
- "((()))())" → 8
6.3 性能优化技巧
- 提前终止:当剩余字符数 ≤ 当前max_len时可提前结束
- 空间优化:双指针法空间最优
- 并行处理:对于超长字符串可考虑分段处理
7. 题目变种与扩展
7.1 变种题目
- 统计所有有效括号子串数量(LeetCode 678)
- 包含通配符的括号验证(LeetCode 678)
- 生成所有有效括号组合(LeetCode 22)
7.2 实际应用场景
- 代码语法检查(括号匹配验证)
- 配置文件解析(嵌套结构处理)
- 数学表达式求值
我在实际刷题中发现,这类括号问题有通用解题模式:
- 栈适合处理嵌套结构
- DP适合处理最优子结构问题
- 双指针适合处理对称性问题
对于面试准备,建议重点掌握栈解法,并理解如何从暴力解法逐步优化到最优解的过程。这类问题的核心在于发现括号匹配的规律性,将全局问题分解为局部子问题求解。