(LeetCode-Hot100)32. 最长有效括号

问题简介

🔗 LeetCode 32. 最长有效括号

题目描述

给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。


示例说明

示例 1:

复制代码
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"

示例 2:

复制代码
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"

示例 3:

复制代码
输入:s = ""
输出:0

解题思路

📌 本题要求找出最长连续有效括号子串的长度。有效括号即合法的括号匹配序列。

我们可以采用以下几种主流方法:


💡 方法一:动态规划(DP)

核心思想:

定义 dp[i] 表示以 s[i] 结尾的最长有效括号子串长度。

  • 如果 s[i] == '(',则 dp[i] = 0(不可能以左括号结尾形成有效括号)。
  • 如果 s[i] == ')'
    • 情况1:s[i-1] == '('dp[i] = dp[i-2] + 2
    • 情况2:s[i-1] == ')'s[i - dp[i-1] - 1] == '('
      dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2]

注意边界检查!


💡 方法二:栈(Stack)

核心思想:

用栈存储下标,初始压入 -1 作为基准。

  • 遇到 '(':压入当前下标。
  • 遇到 ')'
    • 弹出栈顶。
    • 若栈为空:说明当前 ')' 无法匹配,压入当前下标作为新基准。
    • 否则:当前有效长度 = i - stack.peek(),更新最大值。

栈中始终保存"最后一个未匹配的右括号位置"或起始点。


💡 方法三:双指针(左右扫描)

核心思想:

从左到右扫描,记录 leftright 括号数量:

  • left == right:更新最大长度为 2 * right
  • right > left:重置计数器(说明右括号多,非法)

但仅从左到右会漏掉如 "(()" 这种情况(左多右少),所以还需从右到左再扫一次 ,此时当 left > right 时重置。

两次扫描覆盖所有情况。


代码实现

java 复制代码
// Java 实现

class Solution {
    // 方法一:动态规划
    public int longestValidParentheses(String s) {
        if (s == null || s.length() < 2) return 0;
        int n = s.length();
        int[] dp = new int[n];
        int maxLen = 0;

        for (int i = 1; i < n; i++) {
            if (s.charAt(i) == ')') {
                if (s.charAt(i - 1) == '(') {
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                } else if (dp[i - 1] > 0) {
                    int matchIndex = i - dp[i - 1] - 1;
                    if (matchIndex >= 0 && s.charAt(matchIndex) == '(') {
                        dp[i] = dp[i - 1] + 2 + (matchIndex > 0 ? dp[matchIndex - 1] : 0);
                    }
                }
                maxLen = Math.max(maxLen, dp[i]);
            }
        }
        return maxLen;
    }

    // 方法二:栈
    public int longestValidParenthesesStack(String s) {
        Stack<Integer> stack = new Stack<>();
        stack.push(-1); // 基准
        int maxLen = 0;

        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;
    }

    // 方法三:双指针
    public int longestValidParenthesesTwoPass(String s) {
        int left = 0, right = 0, maxLen = 0;

        // Left to Right
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') left++;
            else right++;
            if (left == right) {
                maxLen = Math.max(maxLen, 2 * right);
            } else if (right > left) {
                left = right = 0;
            }
        }

        left = right = 0;
        // Right to Left
        for (int i = s.length() - 1; i >= 0; i--) {
            if (s.charAt(i) == '(') left++;
            else right++;
            if (left == right) {
                maxLen = Math.max(maxLen, 2 * left);
            } else if (left > right) {
                left = right = 0;
            }
        }

        return maxLen;
    }
}
go 复制代码
// Go 实现

func longestValidParentheses(s string) int {
    // 方法一:动态规划
    if len(s) < 2 {
        return 0
    }
    n := len(s)
    dp := make([]int, n)
    maxLen := 0

    for i := 1; i < n; i++ {
        if s[i] == ')' {
            if s[i-1] == '(' {
                if i >= 2 {
                    dp[i] = dp[i-2] + 2
                } else {
                    dp[i] = 2
                }
            } else if dp[i-1] > 0 {
                matchIndex := i - dp[i-1] - 1
                if matchIndex >= 0 && s[matchIndex] == '(' {
                    dp[i] = dp[i-1] + 2
                    if matchIndex > 0 {
                        dp[i] += dp[matchIndex-1]
                    }
                }
            }
            if dp[i] > maxLen {
                maxLen = dp[i]
            }
        }
    }
    return maxLen
}

// 方法二:栈
func longestValidParenthesesStack(s string) int {
    stack := []int{-1} // 基准
    maxLen := 0

    for i, ch := range s {
        if ch == '(' {
            stack = append(stack, i)
        } else {
            stack = stack[:len(stack)-1] // pop
            if len(stack) == 0 {
                stack = append(stack, i) // 新起点
            } else {
                if i-stack[len(stack)-1] > maxLen {
                    maxLen = i - stack[len(stack)-1]
                }
            }
        }
    }
    return maxLen
}

// 方法三:双指针
func longestValidParenthesesTwoPass(s string) int {
    left, right, maxLen := 0, 0, 0

    // Left to Right
    for i := 0; i < len(s); i++ {
        if s[i] == '(' {
            left++
        } else {
            right++
        }
        if left == right {
            if 2*right > maxLen {
                maxLen = 2 * right
            }
        } else if right > left {
            left, right = 0, 0
        }
    }

    left, right = 0, 0
    // Right to Left
    for i := len(s) - 1; i >= 0; i-- {
        if s[i] == '(' {
            left++
        } else {
            right++
        }
        if left == right {
            if 2*left > maxLen {
                maxLen = 2 * left
            }
        } else if left > right {
            left, right = 0, 0
        }
    }

    return maxLen
}

示例演示

s = ")()())" 为例:

  • DP 过程:

    i: 0 1 2 3 4 5
    s: ) ( ) ( ) )
    dp:0 0 2 0 4 0 → max = 4

  • 栈过程:

    i=-1: stack=[-1]
    i=0: ')' → pop → stack empty → push 0 → stack=[0]
    i=1: '(' → push → [0,1]
    i=2: ')' → pop → stack=[0] → len=2-0=2
    i=3: '(' → push → [0,3]
    i=4: ')' → pop → stack=[0] → len=4-0=4 → max=4
    i=5: ')' → pop → stack empty → push 5

  • 双指针:

  • 左→右:在 i=4 时 left=2, right=2 → len=4

  • 右→左:无更优解

✅ 所有方法均得 4


答案有效性证明

方法 正确性依据
✅ DP 状态转移覆盖所有以 ) 结尾的有效子串,且利用已有最优解构建当前解
✅ 栈 利用栈天然匹配特性,通过下标差计算长度,基准点确保连续性
✅ 双指针 两次扫描分别处理"右多"和"左多"情况,数学上完备

所有方法均能处理边界如 "", "(", ")", "(()", ")()())" 等。


复杂度分析

方法 时间复杂度 空间复杂度
💡 动态规划 O(n) O(n)
💡 栈 O(n) O(n)
💡 双指针 O(n) O(1) ✅(最优空间)

双指针法在空间上最优,适合内存敏感场景。


问题总结

📌 关键洞察:

  • 有效括号必须以 ')' 结尾。
  • 连续性要求不能跳过字符。
  • 单向扫描会遗漏"左括号过剩"情况,需双向处理。

推荐解法:

  • 面试首选 (直观、易写、通用性强)
  • 追求空间效率用 双指针
  • DP 适合展示动态规划思维

常见错误:

  • 忘记边界检查(如 i-2 < 0
  • 栈未初始化 -1
  • 双指针只做单向扫描

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
lifallen1 小时前
CDQ 分治 (CDQ Divide and Conquer)
java·数据结构·算法
笨蛋不要掉眼泪2 小时前
OpenFeign远程调用详解:声明式实现、第三方API集成与负载均衡对比
java·运维·负载均衡
yaoxin5211232 小时前
326. Java Stream API - 实现自定义的 toList() 与 toSet() 收集器
java·开发语言
追随者永远是胜利者2 小时前
(LeetCode-Hot100)31. 下一个排列
java·算法·leetcode·职场和发展·go
Cosmoshhhyyy2 小时前
《Effective Java》解读第40条:坚持使用Override注解
java·开发语言
ValhallaCoder2 小时前
hot100-二分查找
数据结构·python·算法·二分查找
0 0 02 小时前
【C++】矩阵翻转/n*n的矩阵旋转
c++·线性代数·算法·矩阵
m0_531237172 小时前
C语言-指针,结构体
c语言·数据结构·算法
癫狂的兔子2 小时前
【Python】【机器学习】十大算法简介与应用
python·算法·机器学习