2026-10-06:统计从两个字符串形成目标字符串的不同方案数。用go语言,给定两个字符串 word1、word2,以及一个目标字符串 target。现在需要从 word1 和 word2 中挑选字符,按顺序组成 target。
要求如下:
target 中的每一个字符,都必须由 word1 或 word2 中某个相同的字符来提供。
如果在组成 target 时,某个字符取自 word1,那么所有取自 word1 的字符在原字符串中的位置必须依次向后,不能相同,也不能倒退。
如果在组成 target 时,某个字符取自 word2,那么所有取自 word2 的字符在原字符串中的位置也必须依次向后,不能相同,也不能倒退。
最终不能只使用其中一个字符串,word1 和 word2 都必须至少贡献一个字符。
两种选择方案是否不同,取决于 target 的每一个位置到底选了哪个字符串,以及选的是该字符串中的哪一个下标。只要有一个位置对应的来源字符串不同,或者下标不同,就算不同方案。
求所有满足条件的方案数量。由于结果可能很大,最后需要对 10 的 9 次方加 7 取余,并返回取余后的结果。
1 <= word1.length, word2.length, target.length <= 100。
word1、word2 和 target 仅由小写英文字母组成。
输入: word1 = "abc", word2 = "bac", target = "abc"。
输出: 5。
解释:
有 5 种形成 target 的方案:
word1[0] = 'a', word1[1] = 'b', word2[2] = 'c'
word1[0] = 'a', word2[0] = 'b', word1[2] = 'c'
word1[0] = 'a', word2[0] = 'b', word2[2] = 'c'
word2[1] = 'a', word1[1] = 'b', word1[2] = 'c'
word2[1] = 'a', word1[1] = 'b', word2[2] = 'c'
所有方案都保持了每个字符串内部递增的下标顺序,并且从每个字符串中至少选择了一个字符。
题目来自力扣3981。#=
大体步骤如下:
1. 明确状态含义
定义三个维度来表示动态规划的状态:
- 第一个维度
i:表示当前已经匹配到目标字符串target的前i个字符。 - 第二个维度
j:表示当前允许使用word1的前j - 1个字符,也就是下标从0到j - 2的字符。j的取值范围是1到m1 + 1,其中m1是word1的长度。 - 第三个维度
k:表示当前允许使用word2的前k - 1个字符,也就是下标从0到k - 2的字符。k的取值范围是1到m2 + 1,其中m2是word2的长度。
因此,状态 f[i][j][k] 表示:在只允许使用 word1 的前 j - 1 个字符和 word2 的前 k - 1 个字符的条件下,能够形成 target 前 i 个字符的方案总数。
2. 初始化空目标字符串的情况
当 i = 0 时,target 为空字符串,不需要选择任何字符。只要允许使用的字符范围不是完全为空(即 j >= 1 且 k >= 1),就都有一种方案:什么都不选。
所以,对于所有 j 从 1 到 m1 + 1,k 从 1 到 m2 + 1,都令 f[0][j][k] = 1。
3. 逐字符处理目标字符串
外层循环依次处理 target 的每一个字符。假设当前处理的是 target 的第 i 个字符(从 0 开始计数),字符为 ch。
接下来要计算 f[i + 1][j + 1][k + 1],即匹配了前 i + 1 个目标字符,并且允许使用 word1 前 j 个字符(下标 0 到 j - 1)和 word2 前 k 个字符(下标 0 到 k - 1)的方案数。
这里 j 从 0 遍历到 m1,k 从某个下界开始遍历到 m2。下界是 max(0, i + 1 - j),因为总共可用的字符数是 j + k,要匹配 i + 1 个目标字符,必须满足 j + k >= i + 1。
4. 利用二维前缀和进行容斥
计算 f[i + 1][j + 1][k + 1] 时,先考虑不依赖新加入的 word1 第 j 个字符和 word2 第 k 个字符的情况。这可以通过二维前缀和的容斥得到:
f[i + 1][j][k + 1]:允许word1前j - 1个字符,word2前k个字符。f[i + 1][j + 1][k]:允许word1前j个字符,word2前k - 1个字符。f[i + 1][j][k]:允许word1前j - 1个字符,word2前k - 1个字符。
基础方案数为:
f[i + 1][j][k + 1] + f[i + 1][j + 1][k] - f[i + 1][j][k]
这部分涵盖了所有不使用新字符来匹配当前目标字符的方案。
5. 加上使用 word1 新字符匹配当前目标字符的方案
如果当前目标字符 ch 等于 word1 中下标为 j - 1 的字符(前提是 j > 0),那么可以用这个字符来匹配 ch。此时,之前匹配 target 前 i 个字符时,必须恰好使用了 word2 中下标为 k - 1 的字符。这种方案数等于:
f[i][j][k + 1] - f[i][j][k]
解释:f[i][j][k + 1] 允许 word1 前 j - 1 个字符,word2 前 k 个字符;f[i][j][k] 允许 word1 前 j - 1 个字符,word2 前 k - 1 个字符。两者之差正好是必须使用 word2 第 k 个字符的方案数。这样加上后,就补全了所有使用 word1 第 j 个字符匹配当前字符的方案。
6. 加上使用 word2 新字符匹配当前目标字符的方案
类似地,如果当前目标字符 ch 等于 word2 中下标为 k - 1 的字符(前提是 k > 0),则可以用这个字符匹配 ch。此时,之前匹配 target 前 i 个字符时,必须恰好使用了 word1 中下标为 j - 1 的字符。这种方案数等于:
f[i][j + 1][k] - f[i][j][k]
解释:f[i][j + 1][k] 允许 word1 前 j 个字符,word2 前 k - 1 个字符;f[i][j][k] 允许 word1 前 j - 1 个字符,word2 前 k - 1 个字符。两者之差正好是必须使用 word1 第 j 个字符的方案数。加上后,就补全了所有使用 word2 第 k 个字符匹配当前字符的方案。
7. 取模并完成状态转移
将上述所有部分相加,得到 f[i + 1][j + 1][k + 1] 的原始值,然后对 10^9 + 7 取模,保证结果在整数范围内。
8. 计算最终答案
当所有目标字符处理完毕后,f[n][m1 + 1][m2 + 1] 表示使用 word1 全部字符和 word2 全部字符时,形成整个 target 的方案总数。但这个总数包含了只使用 word1 或只使用 word2 的情况。
题目要求两个字符串都必须至少贡献一个字符,所以需要排除:
- 只使用
word1的方案:对应word2允许使用前0个字符,即k = 1,所以是f[n][m1 + 1][1]。 - 只使用
word2的方案:对应word1允许使用前0个字符,即j = 1,所以是f[n][1][m2 + 1]。
因此最终答案:
ans = f[n][m1 + 1][m2 + 1] - f[n][m1 + 1][1] - f[n][1][m2 + 1]
由于取模可能导致负数,最后再对 10^9 + 7 取模并调整到非负范围。
复杂度分析
-
时间复杂度:
外层循环遍历target的每个字符,共n次;中层循环遍历word1的允许前缀长度,共m1 + 1次;内层循环遍历word2的允许前缀长度,虽然下界是max(0, i + 1 - j),但最坏情况下接近m2次。因此总操作次数为O(n * m1 * m2)。每次操作都是常数时间的加减和取模,所以总时间复杂度为O(n * m1 * m2)。 -
额外空间复杂度:
使用了一个三维数组f,大小为(n + 1) * (m1 + 2) * (m2 + 2),所以额外空间为O(n * m1 * m2)。没有使用其他大规模数据结构。
由于题目中 n, m1, m2 都不超过 100,最大状态数约为 100^3 = 1,000,000,时间和空间都可以接受。
Go完整代码如下:
package main
import (
"fmt"
)
func interleaveCharacters(word1, word2, target string) int {
const mod = 1_000_000_007
n, m1, m2 := len(target), len(word1), len(word2)
f := make([][][]int, n+1)
for i := range f {
f[i] = make([][]int, m1+2)
for j := range f[i] {
f[i][j] = make([]int, m2+2)
}
}
for j := 1; j < m1+2; j++ {
for k := 1; k < m2+2; k++ {
f[0][j][k] = 1
}
}
for i, ch := range target {
for j := range m1 + 1 {
// j+k >= i+1
for k := max(0, i+1-j); k <= m2; k++ {
res := f[i+1][j][k+1] + f[i+1][j+1][k] - f[i+1][j][k]
if j > 0 && word1[j-1] == byte(ch) {
res += f[i][j][k+1] - f[i][j][k]
}
if k > 0 && word2[k-1] == byte(ch) {
res += f[i][j+1][k] - f[i][j][k]
}
f[i+1][j+1][k+1] = res % mod
}
}
}
ans := f[n][m1+1][m2+1] - f[n][m1+1][1] - f[n][1][m2+1]
return (ans%mod + mod) % mod // 保证 ans 非负
}
func main() {
word1 := "abc"
word2 := "bac"
target := "abc"
result := interleaveCharacters(word1, word2, target)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def interleave_characters(word1: str, word2: str, target: str) -> int:
MOD = 1_000_000_007
n, m1, m2 = len(target), len(word1), len(word2)
# f[i][j][k] 表示处理到 target 的前 i 个字符,
# word1 中已用到前 j 个位置,word2 中已用到前 k 个位置的方案数
f = [[[0] * (m2 + 2) for _ in range(m1 + 2)] for _ in range(n + 1)]
# 初始化:target 为空时,任意前缀状态都算 1 种方案
for j in range(1, m1 + 2):
for k in range(1, m2 + 2):
f[0][j][k] = 1
for i, ch in enumerate(target):
for j in range(m1 + 1):
# k 的下界保证 j + k >= i + 1,即已选字符数足够覆盖 target 的前 i+1 个字符
start_k = max(0, i + 1 - j)
for k in range(start_k, m2 + 1):
res = f[i + 1][j][k + 1] + f[i + 1][j + 1][k] - f[i + 1][j][k]
if j > 0 and word1[j - 1] == ch:
res += f[i][j][k + 1] - f[i][j][k]
if k > 0 and word2[k - 1] == ch:
res += f[i][j + 1][k] - f[i][j][k]
f[i + 1][j + 1][k + 1] = res % MOD
ans = f[n][m1 + 1][m2 + 1] - f[n][m1 + 1][1] - f[n][1][m2 + 1]
return (ans % MOD + MOD) % MOD
if __name__ == "__main__":
word1 = "abc"
word2 = "bac"
target = "abc"
result = interleave_characters(word1, word2, target)
print(result)
C++完整代码如下:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
const int MOD = 1000000007;
int interleaveCharacters(string word1, string word2, string target) {
int n = target.size();
int m1 = word1.size();
int m2 = word2.size();
// f[i][j][k] 表示处理到 target 的前 i 个字符,
// word1 已用到前 j 个位置,word2 已用到前 k 个位置的方案数
vector<vector<vector<int>>> f(
n + 1,
vector<vector<int>>(m1 + 2, vector<int>(m2 + 2, 0))
);
// 初始化:target 为空时,任意前缀状态都算 1 种方案
for (int j = 1; j < m1 + 2; ++j) {
for (int k = 1; k < m2 + 2; ++k) {
f[0][j][k] = 1;
}
}
for (int i = 0; i < n; ++i) {
char ch = target[i];
for (int j = 0; j <= m1; ++j) {
// 保证 j + k >= i + 1
int start_k = max(0, i + 1 - j);
for (int k = start_k; k <= m2; ++k) {
long long res = f[i + 1][j][k + 1]
+ f[i + 1][j + 1][k]
- f[i + 1][j][k];
if (j > 0 && word1[j - 1] == ch) {
res += f[i][j][k + 1] - f[i][j][k];
}
if (k > 0 && word2[k - 1] == ch) {
res += f[i][j + 1][k] - f[i][j][k];
}
f[i + 1][j + 1][k + 1] = res % MOD;
}
}
}
long long ans = f[n][m1 + 1][m2 + 1]
- f[n][m1 + 1][1]
- f[n][1][m2 + 1];
return (ans % MOD + MOD) % MOD; // 保证结果非负
}
int main() {
string word1 = "abc";
string word2 = "bac";
string target = "abc";
int result = interleaveCharacters(word1, word2, target);
cout << result << endl;
return 0;
}