Facebook Pixel

1914. Cyclically Rotating a Grid

MediumArrayMatrixSimulation
Leetcode Link

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:

  1. Process each layer of the matrix from outermost to innermost
  2. For each layer at depth p, extract all perimeter elements in order (top row → right column → bottom row → left column)
  3. Store these elements in a list and rotate the list by k positions using list slicing: nums[k:] + nums[:k]
  4. Place the rotated elements back into their new positions along the perimeter
  5. 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.

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

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:

  1. 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.

  2. 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.

  3. Efficient rotation: Rather than rotating k times individually (which would be O(k × perimeter_length)), we can rotate the extracted array once by k positions using array slicing: nums[k:] + nums[:k]. This is O(perimeter_length).

  4. 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 where k is very large.

  5. 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.

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):

  1. 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.

  2. Optimize rotation count:

    k %= len(nums)
    if k == 0:
        return  # No rotation needed
  3. Perform array rotation using slicing:

    nums = nums[k:] + nums[:k]

    This moves the first k elements to the end, effectively rotating counter-clockwise.

  4. 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]
  • 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 Evaluator

Example 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 takes O(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 to O(m + n) = O(max(m, n))
  • The slicing operation nums[k:] + nums[:k] creates a new list of the same size, which is also O(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 to m - p - 1 (inclusive)
  • Columns: from p to n - 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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

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

Load More