1. 题目链接
2466. 统计构造好字符串的方案数 - 力扣(LeetCode)
2. 题目描述
给你整数 zero
,one
,low
和 high
,我们从空字符串开始构造一个字符串,每一步执行下面操作中的一种:
- 将
'0'
在字符串末尾添加zero
次。 - 将
'1'
在字符串末尾添加one
次。
以上操作可以执行任意次。
如果通过以上过程得到一个 长度 在 low
和 high
之间(包含上下边界)的字符串,那么这个字符串我们称为 好 字符串。
请你返回满足以上要求的 不同 好字符串数目。由于答案可能很大,请将结果对 109 + 7
取余 后返回。
3. 题目示例
示例 1 :
输入:low = 3, high = 3, zero = 1, one = 1
输出:8
解释:
一个可能的好字符串是 "011" 。
可以这样构造得到:"" -> "0" -> "01" -> "011" 。
从 "000" 到 "111" 之间所有的二进制字符串都是好字符串。
示例 2 :
输入:low = 2, high = 3, zero = 1, one = 2
输出:5
解释:好字符串为 "00" ,"11" ,"000" ,"110" 和 "011" 。
4. 解题思路
采用动态规划 + 记忆化搜索(自顶向下递归)。核心思想是:
- 对于目标长度
i
,构造它的方式数目等于以下两种情况之和:- 在长度为
i - zero
的字符串后添加zero
个 0。 - 在长度为
i - one
的字符串后添加one
个 1。
- 在长度为
状态转移方程:
dp[i] = dp[i - zero] + dp[i - one]
关键步骤:
- 递归函数
dfs(i)
计算长度为i
的字符串数目。 - 记忆化数组
memo
缓存已计算的子问题结果。 - 主函数累加
low
到high
范围内的所有结果,并对最终结果取模。
5. 题解代码
class Solution {
private static final int MOD = 1_000_000_007; // 定义模数常量
public int countGoodStrings(int low, int high, int zero, int one) {
int[] memo = new int[high + 1]; // 初始化记忆化数组
Arrays.fill(memo, -1); // 标记为未计算状态
int ans = 0;
for (int i = low; i <= high; i++) { // 遍历所有目标长度
ans = (ans + dfs(i, zero, one, memo)) % MOD; // 累加结果并取模
}
return ans;
}
private int dfs(int i, int zero, int one, int[] memo) {
if (i < 0) { // 长度越界,无效情况
return 0;
}
if (i == 0) { // 长度为0时,空字符串是唯一解
return 1;
}
if (memo[i] != -1) { // 直接返回已缓存的结果
return memo[i];
}
// 计算两种情况之和,并取模
return memo[i] = (dfs(i - zero, zero, one, memo) + dfs(i - one, zero, one, memo)) % MOD;
}
}
6. 复杂度分析
时间复杂度:
- 每个长度
i
(从0
到high
)只会计算一次。 - 每次计算需要两次递归调用(访问
i - zero
和i - one
),但通过记忆化优化后,实际时间复杂度为 O(high)。
空间复杂度:
- 记忆化数组
memo
占用 O(high) 空间。 - 递归调用栈深度最大为 O(high)(最坏情况下)。
综上,空间复杂度为 O(high)。