Main Algorithm:
Looking at this question,
since there is no difference between applying t transformations on separate letters of the same type,
it makes intuitive sense to precompute the number of letters you are left after transforming each letter in the alphabet t times.
But how would you do this?
You can do this by rephrasing the question problem. For example, given the nums array:
we can compute a 26 x 26 matrix to compute the letters formed after 1 transformation.
In this 26 x 26 matrix, each row corresponds to the letters formed after 1 transformation.
For example, given the nums array above, if we take the letter c located at index position 2jI,
since nums[2] = 3, it means the 3 letters after c will be formed. In the matrix,
that specific row (row 3) will look like:
If we do this for all the 26 rows, then we will end up with a transformation matrix, A.
This transformation matrix basically means that, if we want to find the frequency of any letter,
we can just check the index, we can initialize a vector, v, of height 26, with the value at each index being 1,
and then do the operation A * v so that each index is thus the length of each letter after 1 transformation.
For consequent transformations, we can just continue multiplying v by A. This leads us to our first (and only) optimisation (there is too many...)
Square and Multiply Algorithm:
To slightly speed-up the multiplication process, realise that we can convert it to O(log(t)).
Given t = 23, we can convert t into binary to get 0b10111.
Since multiplying v by A 23 times is just A^23 * v
Now, using the binary representation of 23, we get A^23 = A^1 + A^2 + A^4 + A^16.
Overall, that means instead of doing ... * (A * (A * v)) 23 times (which is equivalent to A^23 * v)
we can just continuously square A,
and break down the original equation into
A^23 * v = (A^1 * v) + (A^2 * v) + (A^4 * v) + (A^16 * v).
code
class Solution {
public:
int lengthAfterTransformations(string s, int t, vector<int>& nums) {
vector<vector<long>> matrix (26, vector<long>(26, 0));
vector<vector<long>> v (26, vector<long>(1, 1));
for (int i = 0; i < nums.size(); ++i) {
for (int j = 1; j <= nums[i]; ++j) {
++matrix[i][(i+j)%26];
}
}
int n = t;
while (n > 0) {
if (n%2 == 1) {
v = matrixMultiplication(matrix, v);
}
matrix = matrixMultiplication(matrix, matrix);
n >>= 1;
}
long ans = 0;
for (char& c : s) {
ans = (ans + v[c-'a'][0]) % MOD;
}
return ans;
}
private:
static const int MOD = 1e9+7;
vector<vector<long>> matrixMultiplication(vector<vector<long>> &leftMatrix, vector<vector<long>> &rightMatrix) {
vector<vector<long>> result (leftMatrix.size(), vector<long>(rightMatrix[0].size(), 0));
for (int i = 0; i < leftMatrix.size(); ++i) {
for (int j = 0; j < rightMatrix[0].size(); ++j) {
for (int k = 0; k < rightMatrix.size(); ++k) {
result[i][j] = (result[i][j] + leftMatrix[i][k] * rightMatrix[k][j]) % MOD;
}
}
}
return result;
}
};
complexity
- Time:
O(log(t) + n), wheretis the argumenttandnis the size of the strings- Space:
O(1)
learnings
- Matrix Multiplication goes crazy-
- that was the most useless "Learnings" message ever-