JAVA 实现 Longest Valid Parentheses(最长有效括号)算法


一、项目背景详细介绍

括号匹配问题是编程面试与算法题中非常经典的考察点之一。常见的括号问题有:

  1. 有效括号验证(Valid Parentheses):判断给定括号字符串是否为有效表达式,例如 "()[]{}" 是否有效。

  2. 生成括号(Generate Parentheses):给定 n,生成所有可能的有效括号组合。

  3. 最长有效括号(Longest Valid Parentheses):给定括号字符串,返回其中最长的有效括号子串的长度。

其中,Longest Valid Parentheses(最长有效括号) 被认为是较难的问题之一,因为它不仅需要判断括号是否匹配,还需要在所有可能的子串中找到最长的那一个。

例如:

  • 输入:"(()" → 输出:2(最长有效子串是 "()"

  • 输入:")()())" → 输出:4(最长有效子串是 "()()"

  • 输入:"" → 输出:0

这个问题涉及 动态规划、栈、双指针 等多种解法,是学习算法过程中必做的一道题。


二、项目需求详细介绍

  1. 功能需求

    • 输入:一个仅包含 '('')' 的字符串 s

    • 输出:一个整数,表示 s 中最长有效括号子串的长度。

  2. 输入要求

    • 字符串长度范围:0 <= s.length <= 3 * 10^4

    • 字符串只包含 '('')'

  3. 输出要求

    • 返回一个非负整数,表示最长有效括号子串的长度。

  4. 性能要求

    • 时间复杂度要求接近 O(n)

    • 空间复杂度要求尽可能低。

  5. 测试需求

    • 输入空串。

    • 输入全部无效括号串(如 "((((""))))")。

    • 输入包含多个有效子串(如 ")()())()")。

    • 输入大规模字符串(数万长度)。


三、相关技术详细介绍

为了解决这个问题,我们可以使用多种方法:

1. 栈方法

  • 思路:使用栈存储索引,遇到 '(' 入栈,遇到 ')' 出栈并计算长度。

  • 优点:直观,容易实现。

  • 缺点:需要额外的空间 O(n)。

2. 动态规划方法

  • 状态定义:dp[i] 表示以下标 i 结尾的最长有效括号长度。

  • 状态转移:

    • 如果 s[i] == ')' && s[i-1] == '(',那么 dp[i] = dp[i-2] + 2

    • 如果 s[i] == ')' && s[i-1] == ')' && s[i-dp[i-1]-1] == '(',那么

      
          

      dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2]

  • 优点:时间复杂度 O(n),结果准确。

  • 缺点:需要 O(n) 空间。

3. 双向遍历(贪心方法)

  • 从左向右遍历:统计 '('')' 数量,如果两者相等,更新结果;如果 ')' > '(',重置计数。

  • 从右向左遍历:同样的方法,防止遗漏。

  • 优点:时间复杂度 O(n),空间复杂度 O(1)。

  • 缺点:需要两次遍历。

在本项目中,我们选择 动态规划方法(DP),因为它逻辑清晰,能够保证覆盖所有情况。


四、实现思路详细介绍

  1. 初始化

    • 如果字符串为空或长度小于 2,直接返回 0。

    • 定义数组 dp[n],其中 dp[i] 表示以下标 i 结尾的最长有效括号长度。

  2. 递推公式

    • 情况一:s[i] == ')' && s[i-1] == '('

      
          

      dp[i] = dp[i-2] + 2

    • 情况二:s[i] == ')' && s[i-1] == ')'

      • 如果 s[i - dp[i-1] - 1] == '(',说明形成了新的匹配:

        
              

        dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2]

  3. 结果更新

    • 遍历过程中不断更新 maxLen = Math.max(maxLen, dp[i])

  4. 返回结果

    • 最终返回 maxLen


五、完整实现代码

// ==========================================
// 文件:LongestValidParentheses.java
// 功能:使用动态规划实现最长有效括号算法
// ==========================================

public class LongestValidParentheses {

    /**
     * 动态规划解法:最长有效括号
     * @param s 输入字符串
     * @return 最长有效括号长度
     */
    public int longestValidParentheses(String s) {
        int n = s.length();
        if (n < 2) return 0;

        int[] dp = new int[n]; // dp[i] 表示以 i 结尾的最长有效括号
        int maxLen = 0;

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

    // 主方法:测试
    public static void main(String[] args) {
        LongestValidParentheses solver = new LongestValidParentheses();

        String s1 = "(()";
        String s2 = ")()())";
        String s3 = "";
        String s4 = "((()))";
        String s5 = "()(()";

        System.out.println("测试用例1: " + solver.longestValidParentheses(s1)); // 2
        System.out.println("测试用例2: " + solver.longestValidParentheses(s2)); // 4
        System.out.println("测试用例3: " + solver.longestValidParentheses(s3)); // 0
        System.out.println("测试用例4: " + solver.longestValidParentheses(s4)); // 6
        System.out.println("测试用例5: " + solver.longestValidParentheses(s5)); // 2
    }
}

六、代码详细解读

  1. longestValidParentheses(String s)

    • 输入字符串 s,输出最长有效括号长度。

    • 特判:当 s.length() < 2 时直接返回 0。

    • 定义数组 dp,长度与 s 相同。

  2. 主循环

    • i = 1 开始(因为 i=0 不可能构成有效括号)。

    • s.charAt(i) == ')' 时,可能构成有效括号:

      • 情况1:前一个是 '(',则 dp[i] = dp[i-2] + 2

      • 情况2:前一个是 ')',需要检查 s[i-dp[i-1]-1] 是否是 '(',如果是,则更新。

  3. 更新 maxLen

    • 每次更新 dp[i] 后,更新 maxLen

  4. 返回结果

    • 遍历结束后返回 maxLen

  5. 测试部分

    • 验证了多种输入,包括空串、嵌套括号、多个分段的括号串。


七、项目详细总结

  • 本文介绍了 Longest Valid Parentheses(最长有效括号) 问题的背景与重要性。

  • 我们分析了三种解法:栈、动态规划、双向遍历

  • 最终选择 动态规划解法,实现了 O(n) 时间复杂度的高效算法。

  • 给出了完整 Java 实现,并通过多个测试用例验证了正确性。

该问题是算法面试中的经典题目,考察了 括号匹配、动态规划状态转移、边界处理 等能力。


八、项目常见问题及解答

  1. Q:为什么不用栈方法?
    A:栈方法也可行,但需要额外空间,DP 方法更容易推广到复杂情况。

  2. Q:动态规划为什么只在 s[i] == ')' 时更新?
    A:因为以 '(' 结尾的子串一定不是有效括号。

  3. Q:能否在 O(1) 空间下完成?
    A:可以,使用双向遍历法(从左到右、从右到左各一次),只需两个计数器。

  4. Q:这个题和“有效括号判断”有什么区别?
    A:“有效括号判断”只需返回布尔值,而本题要求找到最长子串,更复杂。

  5. Q:动态规划数组能否去掉?
    A:理论上可以优化,但会丢失状态信息,不推荐。


九、扩展方向与性能优化

  1. 优化为双向遍历 O(1) 空间

    • 使用 leftright 计数器扫描两次即可。

  2. 结合栈与动态规划

    • 在部分复杂题目中,栈能辅助快速找到括号匹配位置,再结合 DP 提高效率。

  3. 扩展到多种括号类型

    • 本题只涉及 '('')',可以扩展到 {}[]

  4. 应用场景

    • 编译器的语法检查(括号闭合)。

    • 正则表达式解析。

    • 数学公式合法性判断。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐