2026-10-06:统计从两个字符串形成目标字符串的不同方案数。用go语言,给定两个字符串 word1、word2,以及一个目标字符串 target。现在需要

0 阅读10分钟

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;
}

在这里插入图片描述