找到所有好字符串
难度:
标签:
题目描述
代码结果
运行时间: 206 ms, 内存: 21.1 MB
/*
* 思路:
* 1. 使用递归方法并结合Stream进行动态规划处理。
* 2. 定义一个递归函数来处理每一个字符位置,同时使用流的特性简化代码。
* 3. 在递归过程中更新状态,判断是否符合好字符串的定义。
*/
import java.util.stream.Stream;
public class GoodStringCounterStream {
private static final int MOD = 1000000007;
private String s1, s2, evil;
private int n, m;
private Integer[][][] memo;
public int countGoodStrings(int n, String s1, String s2, String evil) {
this.s1 = s1;
this.s2 = s2;
this.evil = evil;
this.n = n;
this.m = evil.length();
this.memo = new Integer[n][s1.length() + 1][m + 1];
return dfs(0, 0, 0);
}
private int dfs(int i, int j, int k) {
if (k == m) return 0;
if (i == n) return 1;
if (memo[i][j][k] != null) return memo[i][j][k];
char start = i < s1.length() ? s1.charAt(i) : 'a';
char end = i < s2.length() ? s2.charAt(i) : 'z';
int result = Stream.iterate(start, c -> (char) (c + 1)).limit(end - start + 1)
.mapToInt(c -> {
boolean matchS1 = (j < s1.length() && c == s1.charAt(j));
boolean matchS2 = (j < s2.length() && c == s2.charAt(j));
int newJ = j + (matchS1 ? 1 : 0) - (matchS2 ? 1 : 0);
int newK = (evil.charAt(k) == c) ? k + 1 : 0;
return dfs(i + 1, newJ, newK);
})
.reduce(0, (a, b) -> (a + b) % MOD);
memo[i][j][k] = result;
return result;
}
}
解释
方法:
这道题的解法使用了动态规划和KMP算法的思想。首先通过KMP算法的前缀函数计算出evil字符串的前缀函数,然后基于前缀函数构建一个dp数组,dp[i][j]表示evil字符串的前i个字符与某个字符串匹配,且当前匹配到字符j时,evil字符串的最大匹配长度。接下来使用记忆化搜索,枚举满足条件的字符串,搜索过程中维护当前枚举到的位置i,当前匹配的evil字符串的长度match,以及是否受到s1和s2的字典序限制。搜索到字符串末尾时,如果match小于evil字符串的长度,说明当前字符串是好字符串,累加答案。
时间复杂度:
O(n × m × 26)
空间复杂度:
O(n × m + m × 26)
代码细节讲解
🦆
在计算KMP算法的前缀函数时,为什么需要检查当前字符与j指向的字符不相同时,将j更新为pi[j - 1]?
▷🦆
dp数组中dp[i][j]的定义是基于匹配到字符j时evil字符串的最大匹配长度,那么dp数组的初始状态如何确定,尤其是除了dp[0][ord(pattern[0]) - 97] = 1之外的其他值?
▷🦆
dfs函数中的limit_low和limit_high参数是如何控制字符串的字典序界限的?具体是如何通过这两个参数确保生成的字符串在s1和s2的字典序范围内?
▷🦆
在dfs递归函数中,如果当前的match已经等于或超过了evil字符串的长度,为什么直接返回0,这是基于什么样的考虑?
▷