You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将有效括号字符串(Valid Parenthesis String)问题的递归记忆化(Recursion with memoization)代码转换为表格型(tabulation format)动态规划(DP)解法

如何将有效括号字符串(Valid Parenthesis String)问题的递归记忆化(Recursion with memoization)代码转换为表格型(tabulation format)动态规划(DP)解法

嘿,我来帮你搞定这个转换,顺便解决你头疼的负索引问题!咱们一步步拆解来做:

核心问题:搞定负索引的偏移技巧

原递归里的count可能会变成负数(比如遇到多余的)时),但数组索引不能为负,所以我们需要给count加一个偏移量,把所有可能的count值映射到非负区间。

因为字符串长度是n,count的最大值不会超过n(每个字符都当(的话),最小值不会低于-n(每个字符都当)的话)。我们直接把偏移量设为n,这样:

  • 原count = -n → 偏移后索引是0
  • 原count = 0 → 偏移后索引是n
  • 原count = n → 偏移后索引是2n
    所有索引瞬间都变成非负的,完美解决负索引问题!

表格DP的思路:从后往前倒推

递归是从i=0往i=n(字符串末尾)走,表格DP我们反过来,从i=n开始倒推到i=0:

  1. Base Case:当i == n(所有字符处理完),只有count == 0时是有效的,对应偏移后的索引是n,所以dp[n][n] = true,其他dp[n][*]全为false。
  2. 状态转移:从i = n-1倒推到0,对每个可能的count值(偏移后范围是0到2n),根据当前字符的类型更新dp[i][...]的值。

具体状态转移逻辑

针对不同的字符类型,我们分别处理:

  • 字符是(:当前count会变成count+1,所以dp[i][count + n] = dp[i+1][(count+1) + n](注意要确保count+1不超过n,避免越界)
  • 字符是):当前count会变成count-1,但count-1不能小于0(否则直接无效),所以如果count > 0,dp[i][count + n] = dp[i+1][(count-1) + n],否则dp[i][count + n] = false
  • 字符是*:有三种选择,只要其中一种有效,当前状态就有效:
    1. 当成(:取dp[i+1][(count+1) + n](需保证count+1 ≤n)
    2. 当成):如果count >0,取dp[i+1][(count-1) + n],否则这个选择无效
    3. 当成空字符:直接取dp[i+1][count + n]

最终的表格DP代码

public static boolean checkValidStringTab(String s) {
    int n = s.length();
    int offset = n; // 偏移量,解决负索引问题
    boolean[][] dp = new boolean[n+1][2*n + 1];
    
    // Base Case: 处理完所有字符时,只有count=0(偏移后为n)是有效的
    dp[n][offset] = true;
    
    // 从后往前倒推每个字符
    for (int i = n-1; i >= 0; i--) {
        char c = s.charAt(i);
        // 遍历所有可能的偏移后count值(0到2n)
        for (int shiftedCount = 0; shiftedCount <= 2*n; shiftedCount++) {
            int originalCount = shiftedCount - offset;
            
            if (c == '(') {
                // 原count+1不能超过n,否则状态无效
                if (originalCount + 1 <= n) {
                    dp[i][shiftedCount] = dp[i+1][shiftedCount + 1];
                } else {
                    dp[i][shiftedCount] = false;
                }
            } else if (c == ')') {
                // 原count必须大于0,才能减1不变成负数
                if (originalCount > 0) {
                    dp[i][shiftedCount] = dp[i+1][shiftedCount - 1];
                } else {
                    dp[i][shiftedCount] = false;
                }
            } else { // '*'的三种情况取或
                boolean caseOpen = false;
                if (originalCount + 1 <= n) {
                    caseOpen = dp[i+1][shiftedCount + 1];
                }
                
                boolean caseClose = false;
                if (originalCount > 0) {
                    caseClose = dp[i+1][shiftedCount - 1];
                }
                
                boolean caseEmpty = dp[i+1][shiftedCount];
                
                dp[i][shiftedCount] = caseOpen || caseClose || caseEmpty;
            }
        }
    }
    
    // 初始状态是i=0,原count=0,对应偏移后索引是offset(即n)
    return dp[0][offset];
}

代码小说明

  • dp[i][j]对应原递归中的dp[i][originalCount],其中originalCount = j - offset
  • 倒推的顺序保证了我们计算dp[i]时,dp[i+1]的所有状态已经计算完成,符合状态依赖关系
  • 最后返回dp[0][offset],也就是初始状态(处理第0个字符,count=0)是否有效

备注:内容来源于stack exchange,提问作者Elias El hachem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 09:43:05