把一开始可能想要输入字符串叫做初始字符串。注意这里定义的初始字符串长度没有限制。
- 计算不考虑
的情况下,有多少个初始字符串。 - 计算长度小于
的初始字符串个数。 - 二者相减,即为长度大于等于
的初始字符串个数。
示例 1 的字符串,可以分为
在初始字符串中,每组的长度可以从
假设字符串分为
由于每组的长度至少是
枚举从最后一组中选多少个字母:
- 选
个,问题变成从前 组中选 个字母的方案数。 - 选
个,问题变成从前 组中选 个字母的方案数。
这是一个多重背包问题。相当于有
根据上面的讨论,定义
初始值
假设第
累加得
注意要保证
定义
设一共有
特别地,如果
特别地,如果
代码中用到了一些取模的细节,原理见 模运算的世界:当加减乘除遇上取模。
具体请看 视频讲解,欢迎点赞关注~
class Solution:
def possibleStringCount(self, word: str, k: int) -> int:
n = len(word)
if n < k: # 无法满足要求
return 0
MOD = 1_000_000_007
cnts = []
ans = 1
cnt = 0
for i in range(n):
cnt += 1
if i == n - 1 or word[i] != word[i + 1]:
# 如果 cnt = 1,这组字符串必选,无需参与计算
if cnt > 1:
if k > 0:
cnts.append(cnt - 1)
ans = ans * cnt % MOD
k -= 1 # 注意这里把 k 减小了
cnt = 0
if k <= 0:
return ans
f = [[0] * k for _ in range(len(cnts) + 1)]
f[0][0] = 1
for i, c in enumerate(cnts):
# 计算 f[i] 的前缀和数组 s
s = list(accumulate(f[i], initial=0))
# 计算子数组和
for j in range(k):
f[i + 1][j] = (s[j + 1] - s[max(j - c, 0)]) % MOD
return (ans - sum(f[-1])) % MOD
class Solution {
public int possibleStringCount(String word, int k) {
int n = word.length();
if (n < k) { // 无法满足要求
return 0;
}
final int MOD = 1_000_000_007;
List<Integer> cnts = new ArrayList<>();
long ans = 1;
int cnt = 0;
for (int i = 0; i < n; i++) {
cnt++;
if (i == n - 1 || word.charAt(i) != word.charAt(i + 1)) {
// 如果 cnt = 1,这组字符串必选,无需参与计算
if (cnt > 1) {
if (k > 0) {
cnts.add(cnt - 1);
}
ans = ans * cnt % MOD;
}
k--; // 注意这里把 k 减小了
cnt = 0;
}
}
if (k <= 0) {
return (int) ans;
}
int m = cnts.size();
int[][] f = new int[m + 1][k];
f[0][0] = 1;
int[] s = new int[k + 1];
for (int i = 0; i < m; i++) {
// 计算 f[i] 的前缀和数组 s
for (int j = 0; j < k; j++) {
s[j + 1] = (s[j] + f[i][j]) % MOD;
}
int c = cnts.get(i);
// 计算子数组和
for (int j = 0; j < k; j++) {
f[i + 1][j] = (s[j + 1] - s[Math.max(j - c, 0)]) % MOD;
}
}
for (int x : f[m]) {
ans -= x;
}
return (int) ((ans % MOD + MOD) % MOD); // 保证结果非负
}
}
class Solution {
public:
int possibleStringCount(string word, int k) {
int n = word.length();
if (n < k) { // 无法满足要求
return 0;
}
const int MOD = 1'000'000'007;
vector<int> cnts;
long long ans = 1;
int cnt = 0;
for (int i = 0; i < n; i++) {
cnt++;
if (i == n - 1 || word[i] != word[i + 1]) {
// 如果 cnt = 1,这组字符串必选,无需参与计算
if (cnt > 1) {
if (k > 0) {
cnts.push_back(cnt - 1);
}
ans = ans * cnt % MOD;
}
k--; // 注意这里把 k 减小了
cnt = 0;
}
}
if (k <= 0) {
return ans;
}
int m = cnts.size();
vector<vector<int>> f(m + 1, vector<int>(k));
f[0][0] = 1;
vector<int> s(k + 1);
for (int i = 0; i < m; i++) {
// 计算 f[i] 的前缀和数组 s
for (int j = 0; j < k; j++) {
s[j + 1] = (s[j] + f[i][j]) % MOD;
}
// 计算子数组和
for (int j = 0; j < k; j++) {
f[i + 1][j] = (s[j + 1] - s[max(j - cnts[i], 0)]) % MOD;
}
}
ans -= reduce(f[m].begin(), f[m].end(), 0LL);
return (ans % MOD + MOD) % MOD; // 保证结果非负
}
};
func possibleStringCount(word string, k int) int {
if len(word) < k { // 无法满足要求
return 0
}
const mod = 1_000_000_007
cnts := []int{}
ans := 1
cnt := 0
for i := range word {
cnt++
if i == len(word)-1 || word[i] != word[i+1] {
// 如果 cnt = 1,这组字符串必选,无需参与计算
if cnt > 1 {
if k > 0 {
cnts = append(cnts, cnt-1)
}
ans = ans * cnt % mod
}
k-- // 注意这里把 k 减小了
cnt = 0
}
}
if k <= 0 {
return ans
}
m := len(cnts)
f := make([][]int, m+1)
for i := range f {
f[i] = make([]int, k)
}
f[0][0] = 1
s := make([]int, k+1)
for i, c := range cnts {
// 计算 f[i] 的前缀和数组 s
for j, v := range f[i] {
s[j+1] = (s[j] + v) % mod
}
// 计算子数组和
for j := range f[i+1] {
f[i+1][j] = s[j+1] - s[max(j-c, 0)]
}
}
for _, v := range f[m] {
ans -= v
}
return (ans%mod + mod) % mod // 保证结果非负
}
- 时间复杂度:$\mathcal{O}(n + k^2)$,其中
是 的长度。 - 空间复杂度:$\mathcal{O}(k^2)$。
去掉
前缀和直接计算到
然后和 0-1 背包一样,倒序计算
class Solution:
def possibleStringCount(self, word: str, k: int) -> int:
n = len(word)
if n < k: # 无法满足要求
return 0
MOD = 1_000_000_007
cnts = []
ans = 1
cnt = 0
for i in range(n):
cnt += 1
if i == n - 1 or word[i] != word[i + 1]:
# 如果 cnt = 1,这组字符串必选,无需参与计算
if cnt > 1:
if k > 0: # 保证空间复杂度为 O(k)
cnts.append(cnt - 1)
ans = ans * cnt % MOD
k -= 1 # 注意这里把 k 减小了
cnt = 0
if k <= 0:
return ans
f = [0] * k
f[0] = 1
for c in cnts:
# 原地计算 f 的前缀和
for j in range(1, k):
f[j] = (f[j] + f[j - 1]) % MOD
# 计算子数组和
for j in range(k - 1, c, -1):
f[j] -= f[j - c - 1]
return (ans - sum(f)) % MOD
class Solution {
public int possibleStringCount(String word, int k) {
int n = word.length();
if (n < k) { // 无法满足要求
return 0;
}
final int MOD = 1_000_000_007;
List<Integer> cnts = new ArrayList<>();
long ans = 1;
int cnt = 0;
for (int i = 0; i < n; i++) {
cnt++;
if (i == n - 1 || word.charAt(i) != word.charAt(i + 1)) {
// 如果 cnt = 1,这组字符串必选,无需参与计算
if (cnt > 1) {
if (k > 0) { // 保证空间复杂度为 O(k)
cnts.add(cnt - 1);
}
ans = ans * cnt % MOD;
}
k--; // 注意这里把 k 减小了
cnt = 0;
}
}
if (k <= 0) {
return (int) ans;
}
int[] f = new int[k];
f[0] = 1;
for (int c : cnts) {
// 原地计算 f 的前缀和
for (int j = 1; j < k; j++) {
f[j] = (f[j] + f[j - 1]) % MOD;
}
// 计算子数组和
for (int j = k - 1; j > c; j--) {
f[j] = (f[j] - f[j - c - 1]) % MOD;
}
}
for (int x : f) {
ans -= x;
}
return (int) ((ans % MOD + MOD) % MOD); // 保证结果非负
}
}
class Solution {
public:
int possibleStringCount(string word, int k) {
int n = word.length();
if (n < k) { // 无法满足要求
return 0;
}
const int MOD = 1'000'000'007;
vector<int> cnts;
long long ans = 1;
int cnt = 0;
for (int i = 0; i < n; i++) {
cnt++;
if (i == n - 1 || word[i] != word[i + 1]) {
// 如果 cnt = 1,这组字符串必选,无需参与计算
if (cnt > 1) {
if (k > 0) { // 保证空间复杂度为 O(k)
cnts.push_back(cnt - 1);
}
ans = ans * cnt % MOD;
}
k--; // 注意这里把 k 减小了
cnt = 0;
}
}
if (k <= 0) {
return ans;
}
vector<int> f(k);
f[0] = 1;
for (int c : cnts) {
// 原地计算 f 的前缀和
for (int j = 1; j < k; j++) {
f[j] = (f[j] + f[j - 1]) % MOD;
}
// 计算子数组和
for (int j = k - 1; j > c; j--) {
f[j] = (f[j] - f[j - c - 1]) % MOD;
}
}
ans -= reduce(f.begin(), f.end(), 0LL);
return (ans % MOD + MOD) % MOD; // 保证结果非负
}
};
func possibleStringCount(word string, k int) int {
if len(word) < k { // 无法满足要求
return 0
}
const mod = 1_000_000_007
cnts := []int{}
ans := 1
cnt := 0
for i := range word {
cnt++
if i == len(word)-1 || word[i] != word[i+1] {
// 如果 cnt = 1,这组字符串必选,无需参与计算
if cnt > 1 {
if k > 0 { // 保证空间复杂度为 O(k)
cnts = append(cnts, cnt-1)
}
ans = ans * cnt % mod
}
k-- // 注意这里把 k 减小了
cnt = 0
}
}
if k <= 0 {
return ans
}
f := make([]int, k)
f[0] = 1
for _, c := range cnts {
// 原地计算 f 的前缀和
for j := 1; j < k; j++ {
f[j] = (f[j] + f[j-1]) % mod
}
// 计算子数组和
for j := k - 1; j > c; j-- {
f[j] -= f[j-c-1]
}
}
for _, x := range f {
ans -= x
}
return (ans%mod + mod) % mod // 保证结果非负
}
- 时间复杂度:$\mathcal{O}(n + k^2)$,其中
是 的长度。 - 空间复杂度:$\mathcal{O}(k)$。
更多相似题目,见下面动态规划题单中的「§11.1 前缀和优化 DP」。
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)