每日算法 — 使用java实现最长有效括号:单调栈与动态规划匹配优化

算法背景

给定一个只包含 '(' 和 ')' 的字符串,找出其中最长有效(匹配)括号子串的长度。例如 "(()" 的最长有效括号子串是 "()",长度为2;而 ")()())" 的最长有效括号子串是 "()()",长度为4。

这个问题是字符串处理中的经典题目,主要考察对栈结构的理解以及动态规划的状态设计能力。

核心思路

单调栈法:维护一个栈,存储尚未匹配的括号下标。遇到 '(' 入栈,遇到 ')' 尝试出栈匹配。通过记录匹配区间的长度来更新最大值。

动态规划法:定义 dp[i] 为以第 i 个字符结尾的最长有效括号子串长度。根据 s[i] 是左括号还是右括号,以及前面已匹配的状态进行状态转移。

Java实现

import java.util.Stack;

/**
 * 最长有效括号子串长度计算
 * 提供单调栈法和动态规划法两种实现
 */
public class LongestValidParentheses {

    /**
     * 单调栈法
     * 时间复杂度:O(n)
     * 空间复杂度:O(n)
     */
    public int stackMethod(String s) {
        if (s == null || s.length() == 0) return 0;

        Stack<Integer> stack = new Stack<>();
        // 初始放入-1,作为基准边界
        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;
    }

    /**
     * 动态规划法
     * 时间复杂度:O(n)
     * 空间复杂度:O(n)
     */
    public int dpMethod(String s) {
        if (s == null || s.length() == 0) return 0;

        int n = s.length();
        // dp[i]表示以s[i]结尾的最长有效括号子串长度
        int[] dp = new int[n];
        int maxLen = 0;

        for (int i = 1; i < n; i++) {
            if (s.charAt(i) == ')') {
                if (s.charAt(i - 1) == '(') {
                    // 情形1:...(),直接匹配一对
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                } else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
                    // 情形2:...((...)),当前右括号与前面某个左括号匹配
                    dp[i] = dp[i - 1] + 2;
                    // 加上前面已经匹配的长度
                    if (i - dp[i - 1] >= 2) {
                        dp[i] += dp[i - dp[i - 1] - 2];
                    }
                }
                maxLen = Math.max(maxLen, dp[i]);
            }
            // 以左括号结尾,dp[i]保持为0
        }
        return maxLen;
    }

    /**
     * 双指针法(最优空间复杂度)
     * 时间复杂度:O(n)
     * 空间复杂度:O(1)
     */
    public int twoPointerMethod(String s) {
        if (s == null || s.length() == 0) return 0;

        int left = 0, right = 0, maxLen = 0;

        // 从左向右扫描
        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;
        // 从右向左扫描(处理左括号过多的情况)
        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;
    }

    /**
     * 主函数:测试与演示
     */
    public static void main(String[] args) {
        LongestValidParentheses solver = new LongestValidParentheses();

        String[] testCases = {
            "(()",
            ")()())",
            "",
            "((()))",
            "()(()",
            "())(()"
        };

        System.out.println("三种方法对比测试:\n");
        for (String test : testCases) {
            System.out.printf("输入: "%s"%n", test);
            System.out.printf("  单调栈法: %d%n", solver.stackMethod(test));
            System.out.printf("  动态规划法: %d%n", solver.dpMethod(test));
            System.out.printf("  双指针法: %d%n", solver.twoPointerMethod(test));
            System.out.println();
        }
    }
}

复杂度分析

方法 时间复杂度 空间复杂度 核心思想
单调栈法 O(n) O(n) 利用栈维护未匹配括号的下标边界
动态规划法 O(n) O(n) 状态转移记录以当前位置结尾的最长长度
双指针法 O(n) O(1) 双向扫描统计左右括号数量平衡

双指针法是最优解,通过两次扫描分别处理右括号过多和左括号过多的情况,将空间复杂度优化到O(1)。

应用场景

  • 编译器设计:检查代码括号匹配并定位错误位置
  • 数学表达式解析:验证公式括号的合法性
  • DNA序列分析:配对结构的最长匹配片段分析

总结

本文介绍了求解最长有效括号问题的三种Java实现:单调栈法直观易懂,动态规划法展示了良好的状态设计思想,双指针法则在空间复杂度上达到最优。掌握这三种方法有助于深刻理解栈、DP和双指针三大算法技巧在字符串问题中的灵活应用。