这道题是 LeetCode 2851 题,是一道结合了 KMP字符串匹配、动态规划(DP) 和 矩阵快速幂 的困难级题目。

核心思路解析

1.  字符串循环同构(KMP算法)
    每次操作是将字符串 s 的一个后缀移到开头,这实际上是在对字符串进行循环移位。我们需要知道,s 的所有循环移位结果中,有多少种能够变成目标字符串 t。
    为了高效地找出这些匹配位置,我们将 s 复制一份拼接到自己后面(即 s + s),然后使用 KMP 算法在 s + s 中查找 t 出现的所有位置。假设一共有 match 个位置可以匹配成功。

2.  状态定义与动态规划
    设字符串长度为 n。我们可以定义两个状态:
    *   f[i]:经过 i 次操作后,字符串恰好变为 t 的方案数。
    *   g[i]:经过 i 次操作后,字符串没有变为 t 的方案数。

    状态转移逻辑如下:
    *   如果当前已经是 t(状态 f),下一次操作有 match - 1 种方式保持为 t(不能原地不动,所以要减1),有 n - match 种方式变成非 t。
    *   如果当前不是 t(状态 g),下一次操作有 match 种方式变成 t,有 n - 1 - match 种方式保持为非 t。

    由此得出转移方程:
    f[i] = f[i-1] * (match - 1) + g[i-1] * match
    g[i] = f[i-1] * (n - match) + g[i-1] * (n - 1 - match)

3.  矩阵快速幂优化
    题目中 k 的最大值高达 10^15,普通的循环递推肯定会超时。由于上述 DP 转移方程是线性的,我们可以将其转化为矩阵乘法的形式,并使用矩阵快速幂在 O(log k) 的时间复杂度内求出结果。

Java 代码实现

class Solution {
    private static final int MOD = 1000000007;

    public int numberOfWays(String s, String t, long k) {
        int n = s.length();
        
        // 1. 使用 KMP 算法计算 s 的循环移位中有多少个能匹配 t
        int match = kmpSearch(s, t);

        // 2. 初始化 DP 状态
        // f 表示当前等于 t 的方案数,g 表示当前不等于 t 的方案数
        long f, g;
        if (s.equals(t)) {
            f = 1;
            g = 0;
        } else {
            f = 0;
            g = 1;
        }

        // 3. 构建转移矩阵
        // [f[i], g[i]] = [f[i-1], g[i-1]] * Matrix
        // Matrix = [[match - 1, n - match], 
        //           [match,     n - 1 - match]]
        long[][] matrix = {
            {(match - 1 + MOD) % MOD, (n - match + MOD) % MOD},
            {match, (n - 1 - match + MOD) % MOD}
        };

        // 4. 矩阵快速幂计算 k 次转移后的结果
        long[][] resultMatrix = matrixPower(matrix, k);

        // 5. 计算最终答案 f[k]
        // [f[k], g[k]] = [f[0], g[0]] * resultMatrix
        long ans = (f * resultMatrix[0][0] + g * resultMatrix[1][0]) % MOD;
        return (int) ans;
    }

    // KMP 算法:在 s+s 中查找 t 的出现次数
    private int kmpSearch(String s, String t) {
        int n = s.length();
        int[] next = computeNext(t);
        int matchCount = 0;
        int j = 0; // t 的指针

        // 在 s + s 中匹配,但只需要遍历到 2n - 1 即可
        for (int i = 0; i < 2 * n - 1; i++) {
            char currentS = s.charAt(i % n);
            while (j > 0 && t.charAt(j) != currentS) {
                j = next[j - 1];
            }
            if (t.charAt(j) == currentS) {
                j++;
            }
            if (j == n) {
                matchCount++;
                j = next[j - 1];
            }
        }
        return matchCount;
    }

    // 计算 KMP 的 next 数组
    private int[] computeNext(String pattern) {
        int n = pattern.length();
        int[] next = new int[n];
        int j = 0;
        for (int i = 1; i < n; i++) {
            while (j > 0 && pattern.charAt(i) != pattern.charAt(j)) {
                j = next[j - 1];
            }
            if (pattern.charAt(i) == pattern.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }

    // 2x2 矩阵乘法
    private long[][] multiply(long[][] a, long[][] b) {
        long[][] c = new long[2][2];
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                for (int k = 0; k < 2; k++) {
                    c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD;
                }
            }
        }
        return c;
    }

    // 矩阵快速幂
    private long[][] matrixPower(long[][] matrix, long power) {
        // 初始化为单位矩阵 [[1, 0], [0, 1]]
        long[][] result = {{1, 0}, {0, 1}};
        long[][] base = matrix;
        
        while (power > 0) {
            if ((power & 1) == 1) {
                result = multiply(result, base);
            }
            base = multiply(base, base);
            power >>= 1;
        }
        return result;
    }
}

复杂度分析
*   时间复杂度:O(n + log k)。KMP 预处理和查找的时间复杂度为 O(n),矩阵快速幂的时间复杂度为 O(log k)(因为矩阵大小固定为 2x2,乘法是常数时间)。
*   空间复杂度:O(n)。主要用于存储 KMP 的 next 数组。/

 

更多推荐