Facebook Pixel

2851. String Transformation

HardMathStringDynamic ProgrammingString Matching
LeetCode ↗

Problem Description

You have two strings s and t of equal length n. You can perform a specific operation on string s:

Operation: Remove a suffix from s of length l (where 0 < l < n) and append it to the beginning of s. For example, if s = 'abcd', you can remove the suffix 'cd' and place it at the front, resulting in s = 'cdab'.

Given an integer k, you need to find the number of ways to transform string s into string t using exactly k operations.

Since the answer can be very large, return the result modulo 10^9 + 7.

Key Points:

  • Each operation is essentially a rotation of the string
  • You must use exactly k operations (not fewer, not more)
  • The strings s and t have the same length n
  • In each operation, you must remove at least 1 character but not all characters from the end

Example to understand the operation:

  • Starting with s = 'abcd'
  • Remove suffix of length 1: 'd' → Result: 'dabc'
  • Remove suffix of length 2: 'cd' → Result: 'cdab'
  • Remove suffix of length 3: 'bcd' → Result: 'bcda'

The problem asks you to count all possible sequences of k such operations that can transform s into t.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Dynamic Programming?

This problem maps to Dynamic Programming through a short path in the full flowchart.

Smallconstraints?yesBruteforceenough?noDynamicProgramming

Each operation rotates the string, and counting paths over k steps with two collapsed states is solved by DP plus matrix exponentiation.

Open in Flowchart

Intuition

The key insight is recognizing that each operation is simply a rotation of the string. When we remove a suffix and prepend it, we're rotating the string. For a string of length n, there are exactly n possible rotations (including the original position).

We can represent each rotation by where the original first character ends up. If the first character of s moves to position i, we can use i as the unique identifier for that rotation state.

First observation: We need to identify which rotations of s match string t. There could be multiple rotations that match (or none at all). To find these efficiently, we can use the Z-algorithm on the concatenated string s + t + t. When we find a substring starting at position i (where n ≤ i < 2n) that matches the prefix of length n, then rotation i - n transforms s into t.

Second observation: This is a path counting problem. We need to count paths of exactly k steps from the initial state (rotation 0) to any target state (rotations that match t).

Third observation: The transition structure is special. From rotation 0, we can reach any of the n-1 non-zero rotations in one step. From any non-zero rotation, we can reach rotation 0 or any of the other n-2 non-zero rotations. This symmetry means all non-zero rotations behave identically!

This leads us to simplify the problem to just two states:

  • State 0: rotation is 0
  • State 1: rotation is non-zero

The transitions become:

  • From state 0: can go to state 1 with n-1 ways
  • From state 1: can go to state 0 with 1 way, or stay in state 1 with n-2 ways

This is now a simple 2-state dynamic programming problem that can be solved with matrix exponentiation for efficiency. We compute dp[k][0] and dp[k][1] using the transition matrix [[0, 1], [n-1, n-2]] raised to the k-th power.

Finally, we sum up the contributions: for each rotation that matches t, we add dp[k][0] if it's rotation 0, or dp[k][1] if it's a non-zero rotation.

Pattern Learn more about Math and Dynamic Programming patterns.

Solution Approach

The solution implements several key algorithms and techniques to efficiently solve this problem:

1. Z-Algorithm for Pattern Matching

The getZ method implements the Z-algorithm to find all rotations of s that match t. We concatenate the strings as s + t + t (length 3n) and compute the Z-array:

  • For each position i in range [n, 2n), if z[i] >= n, then rotation (i - n) of s equals t
  • The Z-algorithm efficiently computes the longest prefix match at each position in O(n) time
  • It maintains a window [left, right] representing the rightmost Z-box to avoid redundant comparisons

2. Dynamic Programming with State Reduction

Instead of tracking all n possible rotations, we reduce to 2 states:

  • dp[t][0] = number of ways to reach rotation 0 after exactly t steps
  • dp[t][1] = number of ways to reach any specific non-zero rotation after exactly t steps

The recurrence relations are:

  • dp[t][0] = dp[t-1][1] * (n-1) (from any non-zero rotation, we can reach 0)
  • dp[t][1] = dp[t-1][0] * 1 + dp[t-1][1] * (n-2) (from 0 or from other non-zero rotations)

Initial conditions: dp[0][0] = 1, dp[0][1] = 0

3. Matrix Exponentiation for Fast Computation

To compute dp[k] efficiently for large k, we use matrix multiplication:

[dp[t][0]]   [0      1   ] [dp[t-1][0]]
[dp[t][1]] = [n-1  n-2] * [dp[t-1][1]]

The transition matrix A = [[0, 1], [n-1, n-2]] is raised to the k-th power using fast exponentiation:

  • matrixPower implements binary exponentiation: repeatedly square the matrix and multiply when needed
  • This reduces time complexity from O(k) to O(log k)

4. Modular Arithmetic

All operations use modular arithmetic with MOD = 10^9 + 7:

  • add(x, y): adds two numbers and applies modulo efficiently
  • mul(x, y): multiplies two numbers with modulo
  • These helper functions prevent integer overflow and maintain correctness

5. Final Computation

The numberOfWays method orchestrates the solution:

  1. Compute the transition matrix raised to power k
  2. Extract dp[k][0] and dp[k][1] from the first row
  3. Use Z-algorithm to find all valid target rotations
  4. For each valid rotation r:
    • If r = 0, add dp[k][0] to result
    • If r ≠ 0, add dp[k][1] to result
  5. Return the total count modulo 10^9 + 7

Time Complexity: O(n + log k) - Z-algorithm takes O(n), matrix exponentiation takes O(log k)
Space Complexity: O(n) - for storing the concatenated string and Z-array

Example Walkthrough

Let's walk through a concrete example with s = "aba", t = "baa", and k = 2.

Step 1: Identify which rotations of s match t

First, let's see all possible rotations of s = "aba":

  • Rotation 0: "aba" (original)
  • Rotation 1: "aab" (moved 1 char from end to front)
  • Rotation 2: "baa" (moved 2 chars from end to front)

We can see that rotation 2 matches our target t = "baa".

Using the Z-algorithm on s + t + t = "aba" + "baa" + "baa":

  • At position 5 (which is index 2 in the second copy), we find a match of length 3
  • This confirms rotation 5 - 3 = 2 transforms s into t

Step 2: Set up the state transition model

With n = 3, we have:

  • From rotation 0: can reach rotations 1 or 2 (n-1 = 2 ways)
  • From rotation 1: can reach rotations 0 or 2 (move 1 or 2 chars)
  • From rotation 2: can reach rotations 0 or 1 (move 1 or 2 chars)

We simplify to two states:

  • State 0: at rotation 0
  • State 1: at any non-zero rotation (1 or 2)

Step 3: Calculate dp values for k = 2

Initial state: dp[0][0] = 1, dp[0][1] = 0 (we start at rotation 0)

After 1 operation:

  • dp[1][0] = dp[0][1] × 1 = 0 × 1 = 0
  • dp[1][1] = dp[0][0] × 1 + dp[0][1] × (3-2) = 1 × 1 + 0 × 1 = 1

After 2 operations:

  • dp[2][0] = dp[1][1] × 1 = 1 × 1 = 1
  • dp[2][1] = dp[1][0] × 1 + dp[1][1] × (3-2) = 0 × 1 + 1 × 1 = 1

Using matrix form:

[0  1] × [0  1]   [2  1]
[2  1]   [2  1] = [2  3]

First row gives us: dp[2][0] = 2, dp[2][1] = 1

Step 4: Count valid paths

Our target rotation is 2 (non-zero), so we use dp[2][1] = 1.

Result: 1 way

Let's verify by listing all possible 2-operation sequences:

  1. 0 → 1 → 0 (doesn't end at target)
  2. 0 → 1 → 2 ✓ (ends at rotation 2 = "baa")
  3. 0 → 2 → 0 (doesn't end at target)
  4. 0 → 2 → 1 (doesn't end at target)

Indeed, there's exactly 1 way to reach t = "baa" in exactly 2 operations.


Consider s = "aa", t = "aa", k = 1.

Step 1: Identify matching rotations

  • Rotation 0: "aa" ✓
  • Rotation 1: "aa" ✓

Both rotations match t!

Step 2: Calculate dp values for k = 1

  • dp[1][0] = 0 (can't return to rotation 0 in 1 step)
  • dp[1][1] = 1 (can reach rotation 1 from rotation 0)

Step 3: Count valid paths

  • Rotation 0 contributes: dp[1][0] = 0
  • Rotation 1 contributes: dp[1][1] = 1

Result: 0 + 1 = 1 way

This shows how the algorithm handles cases where multiple rotations of s equal t.

Solution Implementation

1class Solution:
2    MOD: int = 1000000007
3
4    def add(self, x: int, y: int) -> int:
5        """Add two numbers with modulo operation."""
6        x += y
7        if x >= self.MOD:
8            x -= self.MOD
9        return x
10
11    def mul(self, x: int, y: int) -> int:
12        """Multiply two numbers with modulo operation."""
13        return (x * y) % self.MOD
14
15    def getZ(self, s: str) -> List[int]:
16        """
17        Compute Z-array using Z-algorithm.
18        Z[i] represents the length of the longest substring starting from s[i] 
19        which is also a prefix of s.
20        """
21        n = len(s)
22        z_array = [0] * n
23        left = right = 0
24      
25        for i in range(1, n):
26            # If i is within the Z-box, we can use previously computed values
27            if i <= right and z_array[i - left] <= right - i:
28                z_array[i] = z_array[i - left]
29            else:
30                # Calculate Z[i] from scratch or extend beyond the Z-box
31                z_value = max(0, right - i + 1)
32                while i + z_value < n and s[i + z_value] == s[z_value]:
33                    z_value += 1
34                z_array[i] = z_value
35          
36            # Update the Z-box if we've found a longer match
37            if i + z_array[i] - 1 > right:
38                left = i
39                right = i + z_array[i] - 1
40              
41        return z_array
42
43    def matrixMultiply(self, matrix_a: List[List[int]], matrix_b: List[List[int]]) -> List[List[int]]:
44        """
45        Multiply two matrices with modulo operation.
46        matrix_a is m x n, matrix_b is n x p, result is m x p.
47        """
48        rows_a = len(matrix_a)
49        cols_a = len(matrix_a[0])
50        cols_b = len(matrix_b[0])
51      
52        # Initialize result matrix with zeros
53        result = [[0] * cols_b for _ in range(rows_a)]
54      
55        # Perform matrix multiplication
56        for i in range(rows_a):
57            for j in range(cols_b):
58                for k in range(cols_a):
59                    result[i][j] = self.add(result[i][j], 
60                                           self.mul(matrix_a[i][k], matrix_b[k][j]))
61        return result
62
63    def matrixPower(self, matrix: List[List[int]], exponent: int) -> List[List[int]]:
64        """
65        Calculate matrix raised to the power of exponent using fast exponentiation.
66        """
67        size = len(matrix)
68      
69        # Initialize result as identity matrix
70        result = [[0] * size for _ in range(size)]
71        for i in range(size):
72            result[i][i] = 1
73      
74        # Create a copy of the input matrix for manipulation
75        base = [row[:] for row in matrix]
76      
77        # Fast exponentiation
78        while exponent > 0:
79            if exponent & 1:  # If exponent is odd
80                result = self.matrixMultiply(result, base)
81            base = self.matrixMultiply(base, base)
82            exponent >>= 1  # Divide exponent by 2
83          
84        return result
85
86    def numberOfWays(self, s: str, t: str, k: int) -> int:
87        """
88        Count the number of ways to transform string s to string t in exactly k operations.
89        Each operation rotates the string by moving some prefix to the end.
90        """
91        n = len(s)
92      
93        # Calculate dp values using matrix exponentiation
94        # dp[0] = ways to reach position 0 (original string)
95        # dp[1] = ways to reach any non-zero position
96        transition_matrix = [[0, 1], [n - 1, n - 2]]
97        power_matrix = self.matrixPower(transition_matrix, k)
98        dp_values = power_matrix[0]  # First row gives us [dp[k][0], dp[k][1]]
99      
100        # Concatenate strings for Z-algorithm: s + t + t
101        concatenated = s + t + t
102        z_array = self.getZ(concatenated)
103      
104        # Check all possible rotations of t that match s
105        result = 0
106        double_n = n + n
107      
108        for i in range(n, double_n):
109            # If the substring starting at position i matches at least n characters,
110            # it means rotating s by (i - n) positions gives us t
111            if z_array[i] >= n:
112                rotation_amount = i - n
113                # Add the number of ways to achieve this rotation in k steps
114                if rotation_amount == 0:
115                    result = self.add(result, dp_values[0])
116                else:
117                    result = self.add(result, dp_values[1])
118                  
119        return result
120
1class Solution {
2    // Modulo constant for preventing integer overflow
3    private static final int MOD = 1000000007;
4
5    /**
6     * Adds two numbers under modulo operation
7     * @param x first number
8     * @param y second number
9     * @return (x + y) % MOD
10     */
11    private int add(int x, int y) {
12        x += y;
13        if (x >= MOD) {
14            x -= MOD;
15        }
16        return x;
17    }
18
19    /**
20     * Multiplies two numbers under modulo operation
21     * @param x first number
22     * @param y second number
23     * @return (x * y) % MOD
24     */
25    private int mul(long x, long y) {
26        return (int) (x * y % MOD);
27    }
28
29    /**
30     * Computes Z-array for string matching
31     * Z[i] represents the length of the longest substring starting from i
32     * which is also a prefix of the string
33     * @param s input string
34     * @return Z-array
35     */
36    private int[] getZ(String s) {
37        int n = s.length();
38        int[] zArray = new int[n];
39      
40        // left and right maintain the window [left, right] which matches with prefix
41        for (int i = 1, left = 0, right = 0; i < n; ++i) {
42            // Case 1: i is within the current Z-box
43            if (i <= right && zArray[i - left] <= right - i) {
44                zArray[i] = zArray[i - left];
45            } else {
46                // Case 2: i is outside the current Z-box or needs extension
47                int zValue = Math.max(0, right - i + 1);
48              
49                // Extend the match as far as possible
50                while (i + zValue < n && s.charAt(i + zValue) == s.charAt(zValue)) {
51                    zValue++;
52                }
53                zArray[i] = zValue;
54            }
55          
56            // Update the Z-box if we found a longer match
57            if (i + zArray[i] - 1 > right) {
58                left = i;
59                right = i + zArray[i] - 1;
60            }
61        }
62        return zArray;
63    }
64
65    /**
66     * Multiplies two matrices with modulo operation
67     * @param matrixA first matrix
68     * @param matrixB second matrix
69     * @return result of matrixA * matrixB
70     */
71    private int[][] matrixMultiply(int[][] matrixA, int[][] matrixB) {
72        int rows = matrixA.length;
73        int cols = matrixA[0].length;
74        int resultCols = matrixB[0].length;
75        int[][] result = new int[rows][resultCols];
76      
77        // Standard matrix multiplication with modulo
78        for (int i = 0; i < rows; ++i) {
79            for (int j = 0; j < resultCols; ++j) {
80                for (int k = 0; k < cols; ++k) {
81                    result[i][j] = add(result[i][j], mul(matrixA[i][k], matrixB[k][j]));
82                }
83            }
84        }
85        return result;
86    }
87
88    /**
89     * Computes matrix power using binary exponentiation
90     * @param matrix base matrix
91     * @param exponent power to raise the matrix to
92     * @return matrix^exponent
93     */
94    private int[][] matrixPower(int[][] matrix, long exponent) {
95        int size = matrix.length;
96      
97        // Initialize result as identity matrix
98        int[][] result = new int[size][size];
99        for (int i = 0; i < size; ++i) {
100            result[i][i] = 1;
101        }
102      
103        // Create a copy of the input matrix for manipulation
104        int[][] base = new int[size][size];
105        for (int i = 0; i < size; ++i) {
106            System.arraycopy(matrix[i], 0, base[i], 0, size);
107        }
108      
109        // Binary exponentiation
110        while (exponent > 0) {
111            if ((exponent & 1) == 1) {
112                result = matrixMultiply(result, base);
113            }
114            base = matrixMultiply(base, base);
115            exponent >>= 1;
116        }
117        return result;
118    }
119
120    /**
121     * Counts the number of ways to transform string s to string t in exactly k operations
122     * Each operation is a cyclic shift of the string
123     * @param s source string
124     * @param t target string
125     * @param k number of operations
126     * @return number of ways modulo MOD
127     */
128    public int numberOfWays(String s, String t, long k) {
129        int n = s.length();
130      
131        // Dynamic programming transition matrix for cyclic shifts
132        // dp[0] represents ways when shift is 0 (same position)
133        // dp[1] represents ways when shift is non-zero
134        int[] dp = matrixPower(new int[][] {{0, 1}, {n - 1, n - 2}}, k)[0];
135      
136        // Concatenate strings for pattern matching
137        String combined = s + t + t;
138      
139        // Find all positions where t can be obtained from cyclic shifts of s
140        int[] zArray = getZ(combined);
141      
142        int totalLength = n + n;
143        int result = 0;
144      
145        // Check each possible starting position in the concatenated string
146        for (int i = n; i < totalLength; ++i) {
147            // If match length >= n, then t can be obtained from this cyclic shift
148            if (zArray[i] >= n) {
149                // Add corresponding dp value based on shift amount
150                int shift = i - n;
151                result = add(result, dp[shift == 0 ? 0 : 1]);
152            }
153        }
154      
155        return result;
156    }
157}
158
1class Solution {
2private:
3    const int MOD = 1000000007;
4
5    // Add two numbers with modulo operation
6    int addMod(int x, int y) {
7        x += y;
8        if (x >= MOD) {
9            x -= MOD;
10        }
11        return x;
12    }
13
14    // Multiply two numbers with modulo operation
15    int multiplyMod(long long x, long long y) {
16        return (x * y) % MOD;
17    }
18
19    // Compute Z-array using Z-algorithm
20    // Z[i] represents the length of the longest substring starting from s[i] 
21    // which is also a prefix of s
22    vector<int> computeZArray(const string& str) {
23        const int n = str.length();
24        vector<int> zArray(n);
25      
26        // left and right maintain the window [left, right] which matches with prefix
27        int left = 0, right = 0;
28      
29        for (int i = 1; i < n; ++i) {
30            // Case 1: i is within the current Z-box
31            if (i <= right && zArray[i - left] <= right - i) {
32                zArray[i] = zArray[i - left];
33            } 
34            // Case 2: i is outside the current Z-box or needs extension
35            else {
36                // Start matching from position i (or continue from right - i + 1)
37                zArray[i] = max(0, right - i + 1);
38                while (i + zArray[i] < n && str[i + zArray[i]] == str[zArray[i]]) {
39                    zArray[i]++;
40                }
41            }
42          
43            // Update the Z-box if we found a longer match
44            if (i + zArray[i] - 1 > right) {
45                left = i;
46                right = i + zArray[i] - 1;
47            }
48        }
49      
50        return zArray;
51    }
52
53    // Matrix multiplication with modulo operation
54    vector<vector<int>> matrixMultiply(const vector<vector<int>>& matrixA, 
55                                       const vector<vector<int>>& matrixB) {
56        const int rows = matrixA.size();
57        const int cols = matrixA[0].size();
58        const int resultCols = matrixB[0].size();
59      
60        vector<vector<int>> result(rows, vector<int>(resultCols, 0));
61      
62        for (int i = 0; i < rows; ++i) {
63            for (int j = 0; j < cols; ++j) {
64                for (int k = 0; k < resultCols; ++k) {
65                    result[i][k] = addMod(result[i][k], 
66                                         multiplyMod(matrixA[i][j], matrixB[j][k]));
67                }
68            }
69        }
70      
71        return result;
72    }
73
74    // Matrix exponentiation using binary exponentiation
75    vector<vector<int>> matrixPower(const vector<vector<int>>& matrix, long long exponent) {
76        const int size = matrix.size();
77      
78        // Initialize result as identity matrix
79        vector<vector<int>> result(size, vector<int>(size, 0));
80        for (int i = 0; i < size; ++i) {
81            result[i][i] = 1;
82        }
83      
84        // Copy input matrix for manipulation
85        auto base = matrix;
86      
87        // Binary exponentiation
88        while (exponent > 0) {
89            if (exponent & 1) {
90                result = matrixMultiply(result, base);
91            }
92            base = matrixMultiply(base, base);
93            exponent >>= 1;
94        }
95      
96        return result;
97    }
98
99public:
100    int numberOfWays(string s, string t, long long k) {
101        const int n = s.length();
102      
103        // Create transition matrix for the problem
104        // dp[0] represents ways when rotation index is 0 (same position)
105        // dp[1] represents ways when rotation index is not 0 (different position)
106        const auto dpMatrix = matrixPower({{0, 1}, {n - 1, n - 2}}, k)[0];
107      
108        // Concatenate strings for Z-algorithm application
109        // Pattern: s + t + t to find all cyclic matches
110        string concatenated = s + t + t;
111      
112        // Compute Z-array for pattern matching
113        const auto zArray = computeZArray(concatenated);
114      
115        const int totalLength = n + n;
116        int result = 0;
117      
118        // Check each position in the second copy of t
119        for (int i = n; i < totalLength; ++i) {
120            // If we have a full match (Z-value >= n), it means s can be rotated to match t
121            if (zArray[i] >= n) {
122                // Add the number of ways based on whether this is rotation by 0 or not
123                // i - n gives the rotation amount
124                int rotationAmount = i - n;
125                result = addMod(result, dpMatrix[rotationAmount != 0 ? 1 : 0]);
126            }
127        }
128      
129        return result;
130    }
131};
132
1const MOD = 1000000007;
2
3// Add two numbers with modulo operation
4function addMod(x: number, y: number): number {
5    x += y;
6    if (x >= MOD) {
7        x -= MOD;
8    }
9    return x;
10}
11
12// Multiply two numbers with modulo operation
13function multiplyMod(x: number, y: number): number {
14    return (x * y) % MOD;
15}
16
17// Compute Z-array using Z-algorithm
18// Z[i] represents the length of the longest substring starting from str[i] 
19// which is also a prefix of str
20function computeZArray(str: string): number[] {
21    const n = str.length;
22    const zArray: number[] = new Array(n).fill(0);
23  
24    // left and right maintain the window [left, right] which matches with prefix
25    let left = 0;
26    let right = 0;
27  
28    for (let i = 1; i < n; i++) {
29        // Case 1: i is within the current Z-box
30        if (i <= right && zArray[i - left] <= right - i) {
31            zArray[i] = zArray[i - left];
32        } 
33        // Case 2: i is outside the current Z-box or needs extension
34        else {
35            // Start matching from position i (or continue from right - i + 1)
36            zArray[i] = Math.max(0, right - i + 1);
37            while (i + zArray[i] < n && str[i + zArray[i]] === str[zArray[i]]) {
38                zArray[i]++;
39            }
40        }
41      
42        // Update the Z-box if we found a longer match
43        if (i + zArray[i] - 1 > right) {
44            left = i;
45            right = i + zArray[i] - 1;
46        }
47    }
48  
49    return zArray;
50}
51
52// Matrix multiplication with modulo operation
53function matrixMultiply(matrixA: number[][], matrixB: number[][]): number[][] {
54    const rows = matrixA.length;
55    const cols = matrixA[0].length;
56    const resultCols = matrixB[0].length;
57  
58    const result: number[][] = Array(rows).fill(null).map(() => Array(resultCols).fill(0));
59  
60    for (let i = 0; i < rows; i++) {
61        for (let j = 0; j < cols; j++) {
62            for (let k = 0; k < resultCols; k++) {
63                result[i][k] = addMod(result[i][k], 
64                                     multiplyMod(matrixA[i][j], matrixB[j][k]));
65            }
66        }
67    }
68  
69    return result;
70}
71
72// Matrix exponentiation using binary exponentiation
73function matrixPower(matrix: number[][], exponent: number): number[][] {
74    const size = matrix.length;
75  
76    // Initialize result as identity matrix
77    const result: number[][] = Array(size).fill(null).map(() => Array(size).fill(0));
78    for (let i = 0; i < size; i++) {
79        result[i][i] = 1;
80    }
81  
82    // Copy input matrix for manipulation
83    let base = matrix.map(row => [...row]);
84  
85    // Binary exponentiation
86    let exp = exponent;
87    while (exp > 0) {
88        if (exp & 1) {
89            const temp = matrixMultiply(result, base);
90            for (let i = 0; i < size; i++) {
91                for (let j = 0; j < size; j++) {
92                    result[i][j] = temp[i][j];
93                }
94            }
95        }
96        base = matrixMultiply(base, base);
97        exp = Math.floor(exp / 2);
98    }
99  
100    return result;
101}
102
103function numberOfWays(s: string, t: string, k: number): number {
104    const n = s.length;
105  
106    // Create transition matrix for the problem
107    // dp[0] represents ways when rotation index is 0 (same position)
108    // dp[1] represents ways when rotation index is not 0 (different position)
109    const dpMatrix = matrixPower([[0, 1], [n - 1, n - 2]], k)[0];
110  
111    // Concatenate strings for Z-algorithm application
112    // Pattern: s + t + t to find all cyclic matches
113    const concatenated = s + t + t;
114  
115    // Compute Z-array for pattern matching
116    const zArray = computeZArray(concatenated);
117  
118    const totalLength = n + n;
119    let result = 0;
120  
121    // Check each position in the second copy of t
122    for (let i = n; i < totalLength; i++) {
123        // If we have a full match (Z-value >= n), it means s can be rotated to match t
124        if (zArray[i] >= n) {
125            // Add the number of ways based on whether this is rotation by 0 or not
126            // i - n gives the rotation amount
127            const rotationAmount = i - n;
128            result = addMod(result, dpMatrix[rotationAmount !== 0 ? 1 : 0]);
129        }
130    }
131  
132    return result;
133}
134

Time and Space Complexity

Time Complexity: O(n + log k)

The time complexity breaks down into several components:

  1. Z-algorithm (getZ function): O(n) where n is the length of the concatenated string s + t + t (which is 3n). The Z-algorithm processes each character at most twice (once when extending right, once when using previously computed values), resulting in linear time complexity.

  2. Matrix Power (matrixPower function): O(log k) for computing the k-th power of a 2×2 matrix using fast exponentiation. Since the matrix is fixed size (2×2), each matrix multiplication takes O(1) time, and we perform O(log k) multiplications due to the binary exponentiation approach.

  3. Matrix Multiplication (matrixMultiply function): For 2×2 matrices, this is O(1) per multiplication.

  4. Final loop to calculate result: O(n) to iterate through positions from n to 2n and check Z-values.

Overall: O(3n) + O(log k) + O(n) = O(n + log k)

Space Complexity: O(n)

The space complexity consists of:

  1. Concatenated string: O(3n) = O(n) for storing s + t + t

  2. Z-array: O(3n) = O(n) for storing the Z-values

  3. Matrix operations: O(1) since we're working with fixed 2×2 matrices

  4. Other variables: O(1) for counters, indices, and the result

Overall: O(n) + O(n) + O(1) = O(n)

Pattern Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Understanding of Valid Rotations

Pitfall: A common mistake is assuming that we need to check if rotating s by some amount gives us t, when actually we need to check if rotating t gives us s. This confusion arises because the operation moves a suffix of the current string to the front.

Why it happens: The problem states we transform s into t, so intuitively we think about rotating s. However, after k operations starting from s, we need to end up at t. The Z-algorithm checks which rotations of s equal t.

Solution: The code correctly handles this by concatenating s + t + t and checking positions in range [n, 2n). If z[i] >= n for position i, it means the rotation of s by (i - n) positions equals t.

2. Off-by-One Error in Rotation Count

Pitfall: Miscounting the number of valid rotations, especially forgetting that rotation by 0 (identity) and rotation by n (full rotation) are different cases.

Example: For s = "abc", there are exactly 3 unique rotations:

  • Rotation by 0: "abc" (identity)
  • Rotation by 1: "cab"
  • Rotation by 2: "bca"

Solution: The code correctly handles this by:

  • Treating rotation 0 separately with dp[k][0]
  • Having exactly n-1 non-zero rotations, each tracked by dp[k][1]
  • The transition matrix correctly uses n-1 and n-2 for the coefficients

3. Integer Overflow in Matrix Multiplication

Pitfall: When multiplying large numbers in the matrix operations, intermediate results can overflow even before applying modulo, leading to incorrect results.

# Incorrect - can overflow
result[i][j] = (result[i][j] + matrix_a[i][k] * matrix_b[k][j]) % MOD

# Correct - uses helper functions
result[i][j] = self.add(result[i][j], self.mul(matrix_a[i][k], matrix_b[k][j]))

Solution: The code uses dedicated add() and mul() helper functions that handle modulo arithmetic safely, preventing overflow at each step.

4. Inefficient DP State Representation

Pitfall: Tracking all n possible rotation states individually, leading to:

  • Space complexity of O(n × k) for the DP table
  • Time complexity of O(n² × k) for computation

Why it's tempting: It seems natural to track dp[step][rotation] for each possible rotation.

Solution: The code brilliantly reduces states to just 2:

  • State 0: at original position
  • State 1: at any non-zero rotation position

This optimization is valid because all non-zero rotations have symmetric transition probabilities, reducing complexity dramatically.

5. Misunderstanding the Operation Constraint

Pitfall: Forgetting that each operation must move at least 1 character but not all n characters. Some might incorrectly allow:

  • Moving 0 characters (no operation)
  • Moving all n characters (returns to original position)

Solution: The code enforces this by having exactly n-1 valid operations from any position, as seen in the transition matrix. From position 0, we can reach any of the n-1 non-zero positions. From any non-zero position, we can reach 0 or any of the other n-2 non-zero positions.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More