引言
在编程竞赛和算法问题中,我们经常会遇到需要对字符串进行多次转换的问题。本文将介绍一个有趣的问题:给定一个字符串和转换规则,计算经过多次转换后字符串的长度。由于直接模拟会导致性能问题,我们将使用矩阵快速幂来高效解决这个问题。
问题描述
给定:
一个由小写字母组成的字符串
s
一个整数
t
表示转换次数一个长度为26的数组
nums
,其中nums[i]
表示字母'a'+i
的转换规则
转换规则:
将
s[i]
替换为字母表中后续的nums[s[i]-'a']
个连续字符如果超过
'z'
则回绕到字母表开头
要求返回经过 t
次转换后的字符串长度,结果对 10^9+7 取模。
示例分析
假设:
s = "a"
t = 2
nums = [3, 1, 1, ..., 1]
(只有第一个元素是3)
第一次转换:
'a' → 'b' + 'c' + 'd' = "bcd" (长度3)
第二次转换:
'b' → 'c' (长度1)
'c' → 'd' (长度1)
'd' → 'e' (长度1)
总长度 = 1 + 1 + 1 = 3
直接模拟的局限性
直接模拟每次转换的过程对于小的 t
是可行的,但当 t
很大时(比如 10^9),这种方法会非常低效甚至不可行,因为字符串长度会呈指数级增长。
矩阵快速幂解法
思路概述
统计字符频率:记录初始字符串中每个字符的出现次数
构建转换矩阵:表示每个字符如何转换为其他字符
矩阵快速幂:高效计算转换矩阵的
t
次幂计算最终长度:将初始频率向量与转换矩阵相乘,求和得到最终长度
详细步骤
1. 统计字符频率
java
long[] count = new long[26]; for (char c : s.toCharArray()) { count[c - 'a']++; }
2. 构建转换矩阵
对于每个字符 i
,计算它转换为其他字符的分布:
java
long[][] matrix = new long[26][26]; for (int i = 0; i < 26; i++) { int k = nums[i]; int q = k / 26; // 完整循环次数 int r = k % 26; // 剩余字符数 if (r == 0) { // 均匀分布 for (int j = 0; j < 26; j++) { matrix[i][j] = q % MOD; } } else { // 部分循环 int start = (i + 1) % 26; int end = (i + r) % 26; for (int j = 0; j < 26; j++) { boolean inRange; if (start <= end) { inRange = (j >= start && j <= end); } else { inRange = (j >= start || j <= end); } matrix[i][j] = (q + (inRange ? 1 : 0)) % MOD; } } }
3. 矩阵快速幂
java
private long[][] matrixPower(long[][] a, int power) { long[][] result = createIdentityMatrix(); while (power > 0) { if ((power & 1) == 1) { result = multiplyMatrices(result, a); } a = multiplyMatrices(a, a); power >>= 1; } return result; }
4. 计算最终长度
java
long[] finalCount = multiplyVectorMatrix(count, matrixPower); long total = 0; for (long num : finalCount) { total = (total + num) % MOD; } return (int) total;
复杂度分析
时间复杂度:O(26^3 * log t)
矩阵乘法:O(26^3)
快速幂:O(log t)
空间复杂度:O(26^2) 用于存储矩阵
完整代码实现
java
import java.util.List; class Solution { private static final int MOD = 1000000007; private static final int SIZE = 26; public int lengthAfterTransformations(String s, int t, List<Integer> nums) { int[] numsArray = new int[SIZE]; for (int i = 0; i < SIZE; i++) { numsArray[i] = nums.get(i); } if (t == 0) { return s.length() % MOD; } long[] count = new long[SIZE]; for (char c : s.toCharArray()) { count[c - 'a']++; } long[][] matrix = buildMatrix(numsArray); long[][] matrixPower = matrixPower(matrix, t); long[] finalCount = multiplyVectorMatrix(count, matrixPower); long total = 0; for (long num : finalCount) { total = (total + num) % MOD; } return (int) total; } private long[][] buildMatrix(int[] nums) { long[][] matrix = new long[SIZE][SIZE]; for (int i = 0; i < SIZE; i++) { int k = nums[i]; int q = k / 26; int r = k % 26; if (r == 0) { for (int j = 0; j < SIZE; j++) { matrix[i][j] = q % MOD; } } else { int start = (i + 1) % 26; int end = (i + r) % 26; for (int j = 0; j < SIZE; j++) { boolean inRange; if (start <= end) { inRange = (j >= start && j <= end); } else { inRange = (j >= start || j <= end); } matrix[i][j] = (q + (inRange ? 1 : 0)) % MOD; } } } return matrix; } private long[][] matrixPower(long[][] a, int power) { long[][] result = createIdentityMatrix(); while (power > 0) { if ((power & 1) == 1) { result = multiplyMatrices(result, a); } a = multiplyMatrices(a, a); power >>= 1; } return result; } private long[][] createIdentityMatrix() { long[][] matrix = new long[SIZE][SIZE]; for (int i = 0; i < SIZE; i++) { matrix[i][i] = 1; } return matrix; } private long[][] multiplyMatrices(long[][] a, long[][] b) { long[][] result = new long[SIZE][SIZE]; for (int i = 0; i < SIZE; i++) { for (int k = 0; k < SIZE; k++) { if (a[i][k] == 0) continue; for (int j = 0; j < SIZE; j++) { result[i][j] = (result[i][j] + a[i][k] * b[k][j]) % MOD; } } } return result; } private long[] multiplyVectorMatrix(long[] vector, long[][] matrix) { long[] result = new long[SIZE]; for (int j = 0; j < SIZE; j++) { for (int i = 0; i < SIZE; i++) { result[j] = (result[j] + vector[i] * matrix[i][j]) % MOD; } } return result; } }
总结
通过这个问题,我们学习了:
如何将字符串转换问题建模为矩阵运算
使用矩阵快速幂高效处理多次转换
如何处理大数取模问题
这种方法不仅适用于这个问题,还可以推广到其他类似的线性变换问题中。掌握矩阵快速幂技术对解决算法竞赛中的许多问题都大有裨益。