今天我们将在豆包MarsCode AI刷题平台上,完成《小J的字母矩阵问题》和《小N的改数组问题》算法问题, 都使用动态规划去解决
《小J的字母矩阵问题》 描述如下:
解析:
- 使用 动态规划 , 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的改数组问题》 描述如下:
解析:
-
由于需要转变为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次修改
}