《小J的字母矩阵问题》和 《小N的改数组问题》| 豆包MarsCode AI刷题

92 阅读3分钟

今天我们将在豆包MarsCode AI刷题平台上,完成《小J的字母矩阵问题》和《小N的改数组问题》算法问题, 都使用动态规划去解决

《小J的字母矩阵问题》 描述如下:

image.png

解析:

  • 使用 动态规划 , fn[i][j][k][h] 代表左下端点(i,j), 右上端点(k,h) 的矩阵中每个字符出现的情况. 由于小写字符仅仅26个, 根据bit位标记是否存在
  • 为了标记某个矩阵存在重复字符, 可以使用 1<<26 标记

具体实现

public static int solution(int n, int m, String[] s) {
    //判断某个范围内的字母需要最多一次
    //空间换时间
    int[][][][] dp = new int[n][m][n][m];
    int duplicate = 1 << 26;
    int result = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            int curr = 1 << (s[i].charAt(j) - 'a');
            dp[i][j][i][j] = curr;
            result++;
            for (int k = i; k >= 0; k--) {
                for (int h = i == k ? j - 1 : j; h >= 0; h--) {
                    int val = curr;
                    if (i > k) {
                        if ((val & dp[i - 1][j][k][j]) > 0) {
                            val |= duplicate;
                        }
                        val = val | dp[i - 1][j][k][j];
                    }
                    if (j > h) {
                        if ((val & dp[i][j - 1][i][h]) > 0) {
                            val |= duplicate;
                        }
                        val = val | dp[i][j - 1][i][h];
                    }
                    if (i > k && j > h) {
                        if ((val & dp[i - 1][j - 1][k][h]) > 0) {
                            val |= duplicate;
                        }
                        val = val | dp[i - 1][j - 1][k][h];
                    }
                    if (val < duplicate) {
                        result++;
                    }
                    dp[i][j][k][h] = val;
                }
            }
        }
    }
    return result;
}

《小N的改数组问题》 描述如下:

image.png

解析:

  • 由于需要转变为75的倍数, 由于75=25*3, 可以得到以下结果

    • 根据3的倍数定律: 转化后的每一位上的数字相加总和(mod 3)=0
    • 由于结果需要被25整除, 则结尾一定是:00, 25, 50, 75这四种情况
  • 定义dp[K+1][3] 代表处理到当前字符串时, 经过i次转化后结果(mod 3)为j的可能

  • 对于[0,s.length()-1]范围内的字符来说:

    • 当前字符串不变化: 则每一种可能的余数加上当前值curr
    • 当前字符变化:
    dp[j][h] = dp[j][h] + (变化后的值为h的可能性) * dp[j - 1][0]
            + (变化后的值+1为h的可能性) * dp[j - 1][1]
            + (变化后的值+2为h的可能性)  * dp[j - 1][2]
    
  • 注意: 如果元字符的首位为0, 则代表第一位一定需要转化

具体实现

public static int solution(String s, int k) {
    int mod = 1000000007;
    //由于75等于5*5*3
    //1. 为了满足3的倍数, 需要各个位相加mod3=0
    //2. 由于5*5 结尾一定是00 或 25 或 50 或 75 这三个值 余值分别为0, 1, 2, 0
    if (s.length() < 2) {
        return 0;
    }
    long[][] dp = new long[k + 1][3]; //截止到当前处,使用操作i次,结尾余数的个数
    int c0 = s.charAt(s.length() - 1) - '0';
    int c1 = s.charAt(s.length() - 2) - '0';
    //如果长度为2, 则只能变化为75
    for (int i = s.length() == 2 ? 75 : 0; i < 100; i += 25) {
        boolean b1 = (i / 10) == c1;
        boolean b0 = (i % 10) == c0;
        if (b1 && b0) {
            dp[0][i % 3]++;
        } else if (b1 || b0) {
            if (k > 0) {
                dp[1][i % 3]++;
            }
        } else {
            if (k > 1) {
                dp[2][i % 3]++;
            }
        }
    }

    boolean firstZero = s.length() > 2 && s.charAt(0) == '0'; //考虑字符串首位为0
    //range分别代表余数为0,1,2的可能, [0,3,6,9] [1,4,7] [2,5,8]
    int[] range = {4, 3, 3};
    for (int i = 0; i < s.length() - 2; i++) {
        int curr = (s.charAt(i) - '0') % 3;
        range[curr]--;
        if (i == 0 && s.charAt(i) != '0') {
            range[0]--; //首位不能变为0
        }
        for (int j = Math.min(k, i + 3); j >= 0; j--) {
            //不改变
            if (!firstZero) {
                long way0 = dp[j][0];
                long way1 = dp[j][1];
                long way2 = dp[j][2];
                dp[j][curr] = way0;
                dp[j][(curr + 1) % 3] = way1;
                dp[j][(curr + 2) % 3] = way2;
            } else {
                //第一位必须要变化, 则这里不变化的话,可能性为0
                dp[j][0] = dp[j][1] = dp[j][2] = 0; 
            }
            //改变
            if (j > 0) {
                for (int h = 0; h < 3; h++) {
                    dp[j][h] = (dp[j][h] + range[h] * dp[j - 1][0]
                            + range[(h + 2) % 3] * dp[j - 1][1]
                            + range[(h + 1) % 3] * dp[j - 1][2]) % mod;
                }
            }
        }
        range[0] = 4;
        range[1] = range[2] = 3;
        firstZero = false;
    }
    return (int) dp[k][0]; //恰好K次修改
}