1914. Cyclically Rotating a Grid
Problem Description
You have an m x n
matrix filled with integers, where both m
and n
are even numbers. The matrix can be visualized as having multiple concentric rectangular layers (like rings), where each layer consists of the elements along the perimeter at a certain depth from the outer edge.
The task is to perform k
cyclic rotations on this matrix. A cyclic rotation means:
- Each layer rotates independently
- Elements within a layer move counter-clockwise by one position
- After one rotation, each element takes the place of its adjacent element in the counter-clockwise direction
For example, in a single rotation:
- Top row elements (except the last) move left
- Left column elements (except the first) move up
- Bottom row elements (except the first) move right
- Right column elements (except the last) move down
- The corner elements transition to their adjacent edges
The solution approach:
- Process each layer of the matrix from outermost to innermost
- For each layer at depth
p
, extract all perimeter elements in order (top row → right column → bottom row → left column) - Store these elements in a list and rotate the list by
k
positions using list slicing:nums[k:] + nums[:k]
- Place the rotated elements back into their new positions along the perimeter
- Optimize by using modulo operation
k % len(nums)
since rotating by the perimeter length returns to the original position
The number of layers to process is min(m, n) // 2
, and each layer's rotation is handled independently. The function modifies the grid in-place and returns the rotated matrix.
Intuition
The key insight is recognizing that the matrix consists of independent concentric layers that don't affect each other during rotation. Think of peeling an onion - each layer can rotate separately without disturbing the others.
For each layer, a counter-clockwise rotation is essentially a circular shift of elements along the perimeter. Instead of trying to move elements one by one in place (which would be complex and require temporary storage), we can:
-
Linearize the problem: Extract all elements from a layer's perimeter into a 1D array. This transforms the 2D rotation problem into a simple 1D array rotation.
-
Why this works: The perimeter forms a closed loop. If we traverse it in order (top → right → bottom → left), we get a sequence where rotating means shifting elements along this sequence. The element at position
i
moves to position(i - k) % length
. -
Efficient rotation: Rather than rotating
k
times individually (which would be O(k × perimeter_length)), we can rotate the extracted array once byk
positions using array slicing:nums[k:] + nums[:k]
. This is O(perimeter_length). -
Optimization with modulo: Since rotating by the perimeter length brings elements back to their original positions, we only need to rotate by
k % perimeter_length
. This handles cases wherek
is very large. -
Layer identification: For a layer at depth
p
, the boundaries are:- Top-left:
(p, p)
- Bottom-right:
(m-p-1, n-p-1)
The number of layers is
min(m, n) // 2
because we go inward until we can't form a complete rectangular ring anymore. - Top-left:
The beauty of this approach is that it decomposes a complex 2D rotation into multiple simple 1D array rotations, making the implementation straightforward and efficient.
Solution Approach
The implementation uses a layer-by-layer processing approach with a helper function rotate
that handles the rotation of a single layer:
Main Function Structure:
m, n = len(grid), len(grid[0])
for p in range(min(m, n) >> 1): # Process each layer
rotate(p, k)
return grid
The number of layers is min(m, n) >> 1
(equivalent to min(m, n) // 2
), iterating from the outermost layer (p=0) to the innermost.
Layer Rotation Algorithm (rotate
function):
-
Extract perimeter elements into a 1D array:
nums = [] # Top row (left to right, excluding last element) for j in range(p, n - p - 1): nums.append(grid[p][j]) # Right column (top to bottom, excluding last element) for i in range(p, m - p - 1): nums.append(grid[i][n - p - 1]) # Bottom row (right to left, excluding last element) for j in range(n - p - 1, p, -1): nums.append(grid[m - p - 1][j]) # Left column (bottom to top, excluding last element) for i in range(m - p - 1, p, -1): nums.append(grid[i][p])
Each loop carefully excludes one corner element to avoid duplication. The traversal order ensures continuous counter-clockwise movement along the perimeter.
-
Optimize rotation count:
k %= len(nums) if k == 0: return # No rotation needed
-
Perform array rotation using slicing:
nums = nums[k:] + nums[:k]
This moves the first
k
elements to the end, effectively rotating counter-clockwise. -
Place rotated elements back:
k = 0 # Reuse k as index counter # Same traversal order as extraction for j in range(p, n - p - 1): grid[p][j] = nums[k] k += 1 # ... (similar for other three sides)
Key Implementation Details:
-
Boundary calculations: For layer
p
, the valid ranges are:- Row indices:
[p, m - p - 1]
- Column indices:
[p, n - p - 1]
- Row indices:
-
In-place modification: The grid is modified directly without creating a new matrix, achieving O(1) extra space (excluding the temporary
nums
array). -
Time Complexity: O(m × n) as each element is processed exactly once across all layers.
-
Space Complexity: O(max(m, n)) for the temporary
nums
array that stores one layer's perimeter at a time.
The pattern used here is essentially "decomposition" - breaking down a complex 2D problem into simpler 1D subproblems that can be solved independently and efficiently.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with a 4×4 matrix and k=2 rotations:
Initial Matrix:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Step 1: Process Outer Layer (p=0)
Extract perimeter elements in counter-clockwise order:
- Top row (left to right, excluding last): [1, 2, 3]
- Right column (top to bottom, excluding last): [4, 8, 12]
- Bottom row (right to left, excluding last): [16, 15, 14]
- Left column (bottom to top, excluding last): [13, 9, 5]
Combined array: nums = [1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5]
Rotate by k=2 positions:
- Length = 12, so k % 12 = 2
- After rotation:
nums = [3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 1, 2]
Place back the rotated elements:
3 4 8 12 5 6 7 16 1 10 11 15 2 9 13 14
Step 2: Process Inner Layer (p=1)
Extract perimeter elements for the inner 2×2 box:
- Top row: [6]
- Right column: [7]
- Bottom row: [11]
- Left column: [10]
Combined array: nums = [6, 7, 11, 10]
Rotate by k=2 positions:
- Length = 4, so k % 4 = 2
- After rotation:
nums = [11, 10, 6, 7]
Place back the rotated elements:
3 4 8 12 5 11 10 16 1 6 7 15 2 9 13 14
Final Result:
3 4 8 12 5 11 10 16 1 6 7 15 2 9 13 14
Each element has moved 2 positions counter-clockwise within its respective layer. Notice how:
- Element 1 moved from top-left to left column
- Element 6 moved from top-left of inner layer to bottom-left
- The layers rotate independently without affecting each other
Solution Implementation
1class Solution:
2 def rotateGrid(self, grid: List[List[int]], k: int) -> List[List[int]]:
3 """
4 Rotates each layer of the grid counter-clockwise by k positions.
5
6 Args:
7 grid: 2D matrix to rotate
8 k: Number of positions to rotate each layer
9
10 Returns:
11 The rotated grid
12 """
13 def rotate_layer(layer_index: int, rotation_count: int) -> None:
14 """
15 Rotates a specific layer of the grid counter-clockwise.
16
17 Args:
18 layer_index: Index of the layer (0 for outermost)
19 rotation_count: Number of positions to rotate
20 """
21 # Extract all elements from the current layer in counter-clockwise order
22 layer_elements = []
23
24 # Top row (left to right, excluding last element)
25 for col in range(layer_index, num_cols - layer_index - 1):
26 layer_elements.append(grid[layer_index][col])
27
28 # Right column (top to bottom, excluding last element)
29 for row in range(layer_index, num_rows - layer_index - 1):
30 layer_elements.append(grid[row][num_cols - layer_index - 1])
31
32 # Bottom row (right to left, excluding last element)
33 for col in range(num_cols - layer_index - 1, layer_index, -1):
34 layer_elements.append(grid[num_rows - layer_index - 1][col])
35
36 # Left column (bottom to top, excluding last element)
37 for row in range(num_rows - layer_index - 1, layer_index, -1):
38 layer_elements.append(grid[row][layer_index])
39
40 # Calculate effective rotation (avoid unnecessary full rotations)
41 effective_rotation = rotation_count % len(layer_elements)
42 if effective_rotation == 0:
43 return
44
45 # Rotate the elements
46 layer_elements = layer_elements[effective_rotation:] + layer_elements[:effective_rotation]
47
48 # Place rotated elements back into the grid
49 element_index = 0
50
51 # Top row (left to right, excluding last element)
52 for col in range(layer_index, num_cols - layer_index - 1):
53 grid[layer_index][col] = layer_elements[element_index]
54 element_index += 1
55
56 # Right column (top to bottom, excluding last element)
57 for row in range(layer_index, num_rows - layer_index - 1):
58 grid[row][num_cols - layer_index - 1] = layer_elements[element_index]
59 element_index += 1
60
61 # Bottom row (right to left, excluding last element)
62 for col in range(num_cols - layer_index - 1, layer_index, -1):
63 grid[num_rows - layer_index - 1][col] = layer_elements[element_index]
64 element_index += 1
65
66 # Left column (bottom to top, excluding last element)
67 for row in range(num_rows - layer_index - 1, layer_index, -1):
68 grid[row][layer_index] = layer_elements[element_index]
69 element_index += 1
70
71 # Get grid dimensions
72 num_rows, num_cols = len(grid), len(grid[0])
73
74 # Calculate number of layers (each layer is a rectangular ring)
75 num_layers = min(num_rows, num_cols) // 2
76
77 # Rotate each layer
78 for layer in range(num_layers):
79 rotate_layer(layer, k)
80
81 return grid
82
1class Solution {
2 private int rows;
3 private int cols;
4 private int[][] matrix;
5
6 /**
7 * Rotates the grid layer by layer, where each layer is rotated k positions
8 * @param grid The input 2D matrix to rotate
9 * @param k The number of positions to rotate each layer
10 * @return The rotated grid
11 */
12 public int[][] rotateGrid(int[][] grid, int k) {
13 rows = grid.length;
14 cols = grid[0].length;
15 this.matrix = grid;
16
17 // Calculate the number of layers (concentric rectangles) in the grid
18 int numLayers = Math.min(rows, cols) / 2;
19
20 // Rotate each layer independently
21 for (int layer = 0; layer < numLayers; ++layer) {
22 rotateLayer(layer, k);
23 }
24
25 return grid;
26 }
27
28 /**
29 * Rotates a specific layer of the grid by k positions
30 * @param layer The layer index (0 for outermost layer)
31 * @param k The number of positions to rotate
32 */
33 private void rotateLayer(int layer, int k) {
34 List<Integer> elements = new ArrayList<>();
35
36 // Extract all elements from the current layer in clockwise order
37
38 // Top row (left to right, excluding last element)
39 for (int col = layer; col < cols - layer - 1; ++col) {
40 elements.add(matrix[layer][col]);
41 }
42
43 // Right column (top to bottom, excluding last element)
44 for (int row = layer; row < rows - layer - 1; ++row) {
45 elements.add(matrix[row][cols - layer - 1]);
46 }
47
48 // Bottom row (right to left, excluding last element)
49 for (int col = cols - layer - 1; col > layer; --col) {
50 elements.add(matrix[rows - layer - 1][col]);
51 }
52
53 // Left column (bottom to top, excluding last element)
54 for (int row = rows - layer - 1; row > layer; --row) {
55 elements.add(matrix[row][layer]);
56 }
57
58 // Calculate effective rotation (optimization to avoid unnecessary full rotations)
59 int layerSize = elements.size();
60 k %= layerSize;
61
62 // If no rotation needed, return early
63 if (k == 0) {
64 return;
65 }
66
67 // Place elements back after rotation
68
69 // Top row (left to right, excluding last element)
70 for (int col = layer; col < cols - layer - 1; ++col) {
71 matrix[layer][col] = elements.get(k++ % layerSize);
72 }
73
74 // Right column (top to bottom, excluding last element)
75 for (int row = layer; row < rows - layer - 1; ++row) {
76 matrix[row][cols - layer - 1] = elements.get(k++ % layerSize);
77 }
78
79 // Bottom row (right to left, excluding last element)
80 for (int col = cols - layer - 1; col > layer; --col) {
81 matrix[rows - layer - 1][col] = elements.get(k++ % layerSize);
82 }
83
84 // Left column (bottom to top, excluding last element)
85 for (int row = rows - layer - 1; row > layer; --row) {
86 matrix[row][layer] = elements.get(k++ % layerSize);
87 }
88 }
89}
90
1class Solution {
2public:
3 vector<vector<int>> rotateGrid(vector<vector<int>>& grid, int k) {
4 int rows = grid.size();
5 int cols = grid[0].size();
6
7 // Lambda function to rotate a specific layer of the grid
8 auto rotateLayer = [&](int layer, int rotations) {
9 vector<int> elements;
10
11 // Extract elements from the current layer in clockwise order
12 // Top row (left to right, excluding last element)
13 for (int col = layer; col < cols - layer - 1; ++col) {
14 elements.push_back(grid[layer][col]);
15 }
16
17 // Right column (top to bottom, excluding last element)
18 for (int row = layer; row < rows - layer - 1; ++row) {
19 elements.push_back(grid[row][cols - layer - 1]);
20 }
21
22 // Bottom row (right to left, excluding last element)
23 for (int col = cols - layer - 1; col > layer; --col) {
24 elements.push_back(grid[rows - layer - 1][col]);
25 }
26
27 // Left column (bottom to top, excluding last element)
28 for (int row = rows - layer - 1; row > layer; --row) {
29 elements.push_back(grid[row][layer]);
30 }
31
32 // Calculate effective rotation (avoid unnecessary full rotations)
33 int totalElements = elements.size();
34 rotations %= totalElements;
35
36 // Skip if no rotation needed
37 if (rotations == 0) {
38 return;
39 }
40
41 // Place elements back after rotation
42 int index = rotations;
43
44 // Fill top row
45 for (int col = layer; col < cols - layer - 1; ++col) {
46 grid[layer][col] = elements[index % totalElements];
47 index++;
48 }
49
50 // Fill right column
51 for (int row = layer; row < rows - layer - 1; ++row) {
52 grid[row][cols - layer - 1] = elements[index % totalElements];
53 index++;
54 }
55
56 // Fill bottom row
57 for (int col = cols - layer - 1; col > layer; --col) {
58 grid[rows - layer - 1][col] = elements[index % totalElements];
59 index++;
60 }
61
62 // Fill left column
63 for (int row = rows - layer - 1; row > layer; --row) {
64 grid[row][layer] = elements[index % totalElements];
65 index++;
66 }
67 };
68
69 // Process each concentric layer from outside to inside
70 int totalLayers = min(rows, cols) / 2;
71 for (int layer = 0; layer < totalLayers; ++layer) {
72 rotateLayer(layer, k);
73 }
74
75 return grid;
76 }
77};
78
1/**
2 * Rotates a 2D grid counter-clockwise by k positions layer by layer
3 * @param grid - The 2D array to rotate
4 * @param k - Number of positions to rotate counter-clockwise
5 * @returns The rotated grid
6 */
7function rotateGrid(grid: number[][], k: number): number[][] {
8 const rowCount: number = grid.length;
9 const colCount: number = grid[0].length;
10
11 /**
12 * Rotates a single layer of the grid
13 * @param layerIndex - The index of the layer (0 for outermost)
14 * @param rotations - Number of positions to rotate
15 */
16 const rotateLayer = (layerIndex: number, rotations: number): void => {
17 const elements: number[] = [];
18
19 // Extract elements from the current layer in counter-clockwise order
20 // Top row (left to right, excluding last element)
21 for (let col = layerIndex; col < colCount - layerIndex - 1; col++) {
22 elements.push(grid[layerIndex][col]);
23 }
24
25 // Right column (top to bottom, excluding last element)
26 for (let row = layerIndex; row < rowCount - layerIndex - 1; row++) {
27 elements.push(grid[row][colCount - layerIndex - 1]);
28 }
29
30 // Bottom row (right to left, excluding last element)
31 for (let col = colCount - layerIndex - 1; col > layerIndex; col--) {
32 elements.push(grid[rowCount - layerIndex - 1][col]);
33 }
34
35 // Left column (bottom to top, excluding last element)
36 for (let row = rowCount - layerIndex - 1; row > layerIndex; row--) {
37 elements.push(grid[row][layerIndex]);
38 }
39
40 // Calculate effective rotation
41 const layerSize: number = elements.length;
42 rotations = rotations % layerSize;
43
44 // No rotation needed
45 if (rotations === 0) {
46 return;
47 }
48
49 // Place rotated elements back into the grid
50 let elementIndex: number = rotations;
51
52 // Top row (left to right, excluding last element)
53 for (let col = layerIndex; col < colCount - layerIndex - 1; col++) {
54 grid[layerIndex][col] = elements[elementIndex % layerSize];
55 elementIndex++;
56 }
57
58 // Right column (top to bottom, excluding last element)
59 for (let row = layerIndex; row < rowCount - layerIndex - 1; row++) {
60 grid[row][colCount - layerIndex - 1] = elements[elementIndex % layerSize];
61 elementIndex++;
62 }
63
64 // Bottom row (right to left, excluding last element)
65 for (let col = colCount - layerIndex - 1; col > layerIndex; col--) {
66 grid[rowCount - layerIndex - 1][col] = elements[elementIndex % layerSize];
67 elementIndex++;
68 }
69
70 // Left column (bottom to top, excluding last element)
71 for (let row = rowCount - layerIndex - 1; row > layerIndex; row--) {
72 grid[row][layerIndex] = elements[elementIndex % layerSize];
73 elementIndex++;
74 }
75 };
76
77 // Process each layer from outermost to innermost
78 const layerCount: number = Math.min(rowCount, colCount) >> 1; // Equivalent to Math.floor(Math.min(rowCount, colCount) / 2)
79 for (let layer = 0; layer < layerCount; layer++) {
80 rotateLayer(layer, k);
81 }
82
83 return grid;
84}
85
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 grid.
The algorithm processes the grid layer by layer, starting from the outermost layer and moving inward. For each layer p
, the rotate
function:
- Extracts all elements from the current layer into a list
nums
, which takesO(2(m-2p) + 2(n-2p))
time - Performs rotation by slicing the list, which takes
O(len(nums))
time - Places the rotated elements back into the grid, which takes
O(len(nums))
time
The total number of elements across all layers equals m * n
. Each element is visited exactly once when extracting from the grid and once when placing back, resulting in O(m * n)
time complexity.
Space Complexity: O(max(m, n))
The algorithm uses additional space for:
- The
nums
list that stores elements from one layer at a time - The largest layer (outermost) contains approximately
2m + 2n - 4
elements, which simplifies toO(m + n)
=O(max(m, n))
- The slicing operation
nums[k:] + nums[:k]
creates a new list of the same size, which is alsoO(max(m, n))
Since we only process one layer at a time and the space is reused for each layer, the overall space complexity is O(max(m, n))
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Element Extraction Order
One of the most common mistakes is extracting perimeter elements in the wrong order or including corner elements multiple times. When traversing the perimeter counter-clockwise, each corner element should be included exactly once.
Pitfall Example:
# WRONG: Including corner elements multiple times
for j in range(p, n - p): # Should be n - p - 1
nums.append(grid[p][j])
Solution: Carefully exclude one element at each corner transition:
- Top row: stop at
n - p - 1
(exclude top-right corner) - Right column: stop at
m - p - 1
(exclude bottom-right corner) - Bottom row: stop at
p + 1
(exclude bottom-left corner) - Left column: stop at
p + 1
(exclude top-left corner, which is already included in top row)
2. Forgetting to Optimize Rotation Count
Rotating by the full perimeter length returns elements to their original positions. Without optimization, large values of k
can cause unnecessary iterations.
Pitfall Example:
# WRONG: Rotating without modulo nums = nums[k:] + nums[:k] # Fails when k > len(nums)
Solution: Always use modulo operation before rotating:
k %= len(nums)
if k == 0:
return # Skip unnecessary work
nums = nums[k:] + nums[:k]
3. Off-by-One Errors in Layer Boundaries
Calculating the correct boundaries for each layer is tricky. The inner boundary should shrink symmetrically from both sides.
Pitfall Example:
# WRONG: Incorrect boundary calculation
for j in range(p, n - p): # Should consider -1 for exclusive end
# This includes one extra element
Solution: For layer p
, the valid ranges are:
- Rows: from
p
tom - p - 1
(inclusive) - Columns: from
p
ton - p - 1
(inclusive)
4. Misunderstanding Rotation Direction
The problem specifies counter-clockwise rotation, but it's easy to accidentally implement clockwise rotation.
Pitfall Example:
# WRONG: Clockwise rotation nums = nums[-k:] + nums[:-k] # This rotates in the wrong direction
Solution: For counter-clockwise rotation, use:
nums = nums[k:] + nums[:k] # Elements at the beginning move to the end
5. Incorrect Number of Layers Calculation
The number of layers depends on the smaller dimension, and integer division is crucial.
Pitfall Example:
# WRONG: Using max instead of min
num_layers = max(m, n) // 2 # This processes non-existent layers
Solution: Always use the minimum dimension:
num_layers = min(m, n) // 2
6. Not Handling Edge Cases
Special cases like k = 0
or when the layer has very few elements need proper handling.
Solution: Add early returns for optimization:
effective_rotation = k % len(layer_elements)
if effective_rotation == 0:
return # No rotation needed
What are the most two important steps in writing a depth first search function? (Select 2)
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!