2946. Matrix Similarity After Cyclic Shifts
Problem Description
You are given a matrix mat
of size m x n
(m rows and n columns) containing integers, and an integer k
. The rows of the matrix are indexed starting from 0.
The problem asks you to perform a specific shifting operation on the matrix k
times:
For each operation:
-
Even-indexed rows (rows 0, 2, 4, 6, ...) are cyclically shifted to the left by one position
- In a left cyclic shift, the first element moves to the end, and all other elements move one position to the left
- For example:
[1, 2, 3, 4]
becomes[2, 3, 4, 1]
-
Odd-indexed rows (rows 1, 3, 5, 7, ...) are cyclically shifted to the right by one position
- In a right cyclic shift, the last element moves to the beginning, and all other elements move one position to the right
- For example:
[1, 2, 3, 4]
becomes[4, 1, 2, 3]
After performing these shifting operations k
times, you need to determine if the resulting matrix is identical to the original matrix.
Return true
if the matrix returns to its original state after k
operations, and false
otherwise.
Key insight: The solution leverages the cyclic nature of the shifts. For a row of length n
, shifting left or right n
times brings the row back to its original state. Therefore, we only need to check if shifting by k % n
positions results in the original configuration.
Intuition
The key observation is that cyclic shifts have a periodic nature. If a row has n
elements, then shifting it left or right n
times will bring it back to its original position. This means we don't actually need to simulate k
shifts - we can directly check if the matrix equals itself after k
shifts.
Instead of performing the shifts k
times and then comparing, we can be clever and check if each element in the original matrix appears in the position it would be after k
shifts. This is much more efficient.
For any element at position j
in a row:
- After
k
left shifts, it would be at position(j - k) % n
- After
k
right shifts, it would be at position(j + k) % n
So the approach is to iterate through each element and verify:
-
For even-indexed rows (which shift left): Check if the element at position
j
equals the element that would end up at positionj
afterk
left shifts. This means checking ifmat[i][j]
equalsmat[i][(j - k) % n]
. -
For odd-indexed rows (which shift right): Check if the element at position
j
equals the element that would end up at positionj
afterk
right shifts. This means checking ifmat[i][j]
equalsmat[i][(j + k) % n]
.
The modulo operation % n
handles the cyclic nature - when indices go beyond the row boundaries, they wrap around. We add n
in the left shift formula (j - k + n) % n
to handle negative values correctly in the modulo operation.
If all elements match their expected positions after k
shifts, the matrix returns to its original state, so we return true
. If any element doesn't match, we return false
.
Learn more about Math patterns.
Solution Approach
The solution implements a direct verification approach without actually performing the shifts. Here's how the algorithm works:
-
Get the row length: Store
n = len(mat[0])
as the number of columns in each row. This is used for the modulo operations. -
Iterate through each row and element: Use
enumerate(mat)
to get both the row indexi
and the row content. Then iterate through each element in the row with its positionj
and valuex
. -
Check based on row parity:
-
For odd-indexed rows (
i % 2 == 1
): These rows shift right. Afterk
right shifts, the element currently at positionj
would have come from position(j - k) % n
. So we check ifx != mat[i][(j + k) % n]
. If they don't match, the matrix doesn't return to its original state, so returnFalse
. -
For even-indexed rows (
i % 2 == 0
): These rows shift left. Afterk
left shifts, the element currently at positionj
would have come from position(j + k) % n
. So we check ifx != mat[i][(j - k + n) % n]
. The+ n
is added to handle negative indices correctly with modulo. If they don't match, returnFalse
.
-
-
Return result: If we've checked all elements and found no mismatches, the matrix returns to its original state after
k
operations, so returnTrue
.
The algorithm has:
- Time Complexity:
O(m × n)
wherem
is the number of rows andn
is the number of columns, as we check each element once. - Space Complexity:
O(1)
as we only use a constant amount of extra space for variables.
The beauty of this solution is that it leverages the mathematical property of cyclic shifts to avoid simulating the actual shifting process, making it both elegant and efficient.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the solution works.
Consider the matrix:
mat = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
And k = 3
.
Since each row has length n = 3
, and we're shifting k = 3
times, we're essentially shifting each row by exactly its length. This should bring the matrix back to its original state (since 3 shifts in a row of 3 elements completes a full cycle).
Step-by-step verification:
Row 0 (even-indexed, shifts left):
- For element at position
j=0
with value1
:- After 3 left shifts, position 0 should contain what was at position
(0 + 3) % 3 = 0
- Check:
mat[0][0] = 1
equalsmat[0][0] = 1
✓
- After 3 left shifts, position 0 should contain what was at position
- For element at position
j=1
with value2
:- After 3 left shifts, position 1 should contain what was at position
(1 + 3) % 3 = 1
- Check:
mat[0][1] = 2
equalsmat[0][1] = 2
✓
- After 3 left shifts, position 1 should contain what was at position
- For element at position
j=2
with value3
:- After 3 left shifts, position 2 should contain what was at position
(2 + 3) % 3 = 2
- Check:
mat[0][2] = 3
equalsmat[0][2] = 3
✓
- After 3 left shifts, position 2 should contain what was at position
Row 1 (odd-indexed, shifts right):
- For element at position
j=0
with value4
:- After 3 right shifts, position 0 should contain what was at position
(0 - 3 + 3) % 3 = 0
- Check:
mat[1][0] = 4
equalsmat[1][0] = 4
✓
- After 3 right shifts, position 0 should contain what was at position
- Similar checks for positions 1 and 2 all pass ✓
Row 2 (even-indexed, shifts left):
- Similar to Row 0, all checks pass ✓
Result: Return true
since all elements match their expected positions.
Counter-example with k = 1
:
Same matrix, but k = 1
:
Row 0 (even-indexed, shifts left once):
- Original:
[1, 2, 3]
→ After 1 left shift:[2, 3, 1]
- Checking position
j=0
:- Current value:
1
- After 1 left shift, position 0 should contain what was at position
(0 + 1) % 3 = 1
- Check:
mat[0][0] = 1
should equalmat[0][1] = 2
1 ≠ 2
✗
- Current value:
Since we found a mismatch, we immediately return false
. The matrix does not return to its original state after 1 shift.
Solution Implementation
1class Solution:
2 def areSimilar(self, mat: List[List[int]], k: int) -> bool:
3 """
4 Check if matrix remains the same after k cyclic shifts.
5 Even rows shift left, odd rows shift right.
6
7 Args:
8 mat: 2D matrix of integers
9 k: Number of positions to shift
10
11 Returns:
12 True if matrix is similar after shifts, False otherwise
13 """
14 # Get the number of columns in the matrix
15 num_cols = len(mat[0])
16
17 # Iterate through each row with its index
18 for row_idx, row in enumerate(mat):
19 # Check each element in the current row
20 for col_idx, element in enumerate(row):
21 # For odd-indexed rows (1, 3, 5...), check right shift
22 if row_idx % 2 == 1:
23 # Calculate position after right shift with wraparound
24 shifted_position = (col_idx + k) % num_cols
25 if element != mat[row_idx][shifted_position]:
26 return False
27
28 # For even-indexed rows (0, 2, 4...), check left shift
29 if row_idx % 2 == 0:
30 # Calculate position after left shift with wraparound
31 # Adding num_cols ensures positive result for modulo
32 shifted_position = (col_idx - k + num_cols) % num_cols
33 if element != mat[row_idx][shifted_position]:
34 return False
35
36 # All elements match their shifted positions
37 return True
38
1class Solution {
2 /**
3 * Checks if a matrix remains similar after k cyclic shifts.
4 * - Even rows (0-indexed) are shifted left by k positions
5 * - Odd rows (0-indexed) are shifted right by k positions
6 *
7 * @param mat The input matrix
8 * @param k The number of positions to shift
9 * @return true if the matrix remains the same after shifts, false otherwise
10 */
11 public boolean areSimilar(int[][] mat, int k) {
12 int rowCount = mat.length;
13 int columnCount = mat[0].length;
14
15 // Optimize k to avoid unnecessary full rotations
16 k = k % columnCount;
17
18 // Check each element in the matrix
19 for (int row = 0; row < rowCount; row++) {
20 for (int col = 0; col < columnCount; col++) {
21
22 // For odd rows (1, 3, 5...), check right shift
23 if (row % 2 == 1) {
24 int shiftedPosition = (col + k) % columnCount;
25 if (mat[row][col] != mat[row][shiftedPosition]) {
26 return false;
27 }
28 }
29
30 // For even rows (0, 2, 4...), check left shift
31 if (row % 2 == 0) {
32 // Add columnCount to handle negative modulo correctly
33 int shiftedPosition = (col - k + columnCount) % columnCount;
34 if (mat[row][col] != mat[row][shiftedPosition]) {
35 return false;
36 }
37 }
38 }
39 }
40
41 // All elements match their shifted positions
42 return true;
43 }
44}
45
1class Solution {
2public:
3 bool areSimilar(vector<vector<int>>& mat, int k) {
4 // Get matrix dimensions
5 int numRows = mat.size();
6 int numCols = mat[0].size();
7
8 // Optimize k by taking modulo with number of columns
9 // since shifting by numCols positions returns to original state
10 k %= numCols;
11
12 // Check each row to verify if matrix remains similar after k shifts
13 for (int row = 0; row < numRows; ++row) {
14 // Check each element in the current row
15 for (int col = 0; col < numCols; ++col) {
16 // For odd-indexed rows (1, 3, 5, ...), perform left shift
17 // Left shift by k is equivalent to comparing with element at (col + k) % numCols
18 if (row % 2 == 1) {
19 int shiftedCol = (col + k) % numCols;
20 if (mat[row][col] != mat[row][shiftedCol]) {
21 return false;
22 }
23 }
24
25 // For even-indexed rows (0, 2, 4, ...), perform right shift
26 // Right shift by k is equivalent to comparing with element at (col - k + numCols) % numCols
27 if (row % 2 == 0) {
28 int shiftedCol = (col - k + numCols) % numCols;
29 if (mat[row][col] != mat[row][shiftedCol]) {
30 return false;
31 }
32 }
33 }
34 }
35
36 // All elements match their shifted positions
37 return true;
38 }
39};
40
1/**
2 * Checks if a matrix remains similar after performing k cyclic shifts on alternating rows.
3 * - Even-indexed rows are shifted left by k positions
4 * - Odd-indexed rows are shifted right by k positions
5 *
6 * @param mat - The input matrix to check
7 * @param k - The number of positions to shift
8 * @returns true if the matrix remains the same after shifts, false otherwise
9 */
10function areSimilar(mat: number[][], k: number): boolean {
11 const rowCount: number = mat.length;
12 const columnCount: number = mat[0].length;
13
14 // Optimize k by taking modulo with column count since shifting by n positions
15 // returns to the original position in a cyclic array of length n
16 const effectiveShift: number = k % columnCount;
17
18 // Iterate through each row of the matrix
19 for (let rowIndex = 0; rowIndex < rowCount; ++rowIndex) {
20 // Check each element in the current row
21 for (let colIndex = 0; colIndex < columnCount; ++colIndex) {
22 // For odd-indexed rows (1, 3, 5, ...), check right shift
23 // Compare current element with element at position after right shift
24 if (rowIndex % 2 === 1) {
25 const shiftedPosition: number = (colIndex + effectiveShift) % columnCount;
26 if (mat[rowIndex][colIndex] !== mat[rowIndex][shiftedPosition]) {
27 return false;
28 }
29 }
30
31 // For even-indexed rows (0, 2, 4, ...), check left shift
32 // Compare current element with element at position after left shift
33 // Adding columnCount ensures positive modulo result
34 if (rowIndex % 2 === 0) {
35 const shiftedPosition: number = (colIndex - effectiveShift + columnCount) % columnCount;
36 if (mat[rowIndex][colIndex] !== mat[rowIndex][shiftedPosition]) {
37 return false;
38 }
39 }
40 }
41 }
42
43 // All elements match their shifted positions
44 return true;
45}
46
Time and Space Complexity
Time Complexity: O(m * n)
where m
is the number of rows and n
is the number of columns in the matrix.
The algorithm uses nested loops:
- The outer loop iterates through all
m
rows of the matrix - The inner loop iterates through all
n
elements in each row - For each element, it performs a constant time operation: checking equality with another element in the same row (accessed via modulo indexing)
- The modulo operation
(j + k) % n
or(j - k + n) % n
takesO(1)
time - Array access
mat[i][...]
takesO(1)
time
Therefore, the total time complexity is O(m * n)
.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space:
- Variables
n
,i
,j
, andx
are all primitive types that takeO(1)
space - No additional data structures are created
- The enumeration and iteration are done in-place without creating copies of the data
The space complexity is constant, O(1)
, as it doesn't depend on the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Modulo Calculation for Negative Indices
One of the most common mistakes is incorrectly handling the modulo operation when dealing with left shifts (even-indexed rows). When calculating (col_idx - k) % num_cols
, if col_idx - k
is negative, the modulo operation in some programming languages (like Python) can produce unexpected results.
Incorrect approach:
# This might seem intuitive but can fail with negative values shifted_position = (col_idx - k) % num_cols
Why it fails:
- If
col_idx = 1
,k = 3
, andnum_cols = 5
(1 - 3) % 5 = -2 % 5 = -2
in some implementations- This gives an invalid array index
Correct approach:
# Add num_cols to ensure the result is always positive shifted_position = (col_idx - k + num_cols) % num_cols
2. Confusing Shift Direction Logic
Another pitfall is mixing up which direction to check for verification. The problem states:
- Even rows shift LEFT
- Odd rows shift RIGHT
But when verifying if the matrix returns to original state, you need to think "where did this element come FROM":
- For left shifts: current element at position
j
came from position(j + k) % n
- For right shifts: current element at position
j
came from position(j - k) % n
Incorrect approach:
if row_idx % 2 == 0: # Even row - shifts left # Wrong: checking where element goes TO instead of where it came FROM shifted_position = (col_idx - k + num_cols) % num_cols
Correct approach:
if row_idx % 2 == 0: # Even row - shifts left # Correct: checking where element came FROM after k left shifts shifted_position = (col_idx + k) % num_cols
3. Not Optimizing k with Modulo
While not affecting correctness, failing to optimize k
can lead to unnecessary large calculations:
Suboptimal approach:
# Using k directly without optimization shifted_position = (col_idx + k) % num_cols
Optimized approach:
# Pre-compute k % num_cols once k = k % num_cols shifted_position = (col_idx + k) % num_cols
This optimization is particularly important when k
is very large, as it reduces the computation needed for the modulo operations throughout the algorithm.
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!