Cyclically Rotating a Grid: Layer-by-Layer Simulation
You are given an m x n grid of integers and a positive integer k. Your job is to cyclically rotate each concentric layer of the grid counter-clockwise by k positions and return the resulting grid. That is LeetCode 1914: Cyclically Rotating a Grid, and it is a clean example of the "peel the layer, do something, put it back" pattern that shows up across matrix problems.
If you have worked through spiral matrix traversal before, the layer extraction step will feel familiar. The twist here is that instead of reading elements in spiral order, you rotate them along the perimeter of each layer and write them back.
The problem
You receive an m x n matrix where both m and n are even, and an integer k. The grid has min(m, n) / 2 concentric layers. For each layer, you extract the elements along its perimeter, shift them counter-clockwise by k positions, and place them back. Return the modified grid.
The approach
The core idea breaks down into three repeatable steps per layer:
-
Extract the layer. Walk the perimeter of the current layer (top row left-to-right, right column top-to-bottom, bottom row right-to-left, left column bottom-to-top) and collect the elements into a flat list.
-
Rotate the list. A counter-clockwise rotation by
kpositions is the same as shifting the list left byk. Since the list wraps around, you only need to shift byk % len(layer)positions. A simple slice does the job:layer[k:] + layer[:k]. -
Place elements back. Walk the same perimeter path again, this time writing the rotated elements back into the grid.
You repeat this for every layer, working inward from the outermost ring to the innermost. The number of layers is min(m, n) // 2.
Always reduce k by taking k % perimeter_length for each layer before rotating. The perimeter length varies between layers, and k can be much larger than any of them. Without this modulo step, you would do unnecessary full rotations that produce the same result.
The solution
def rotate_grid(grid: list[list[int]], k: int) -> list[list[int]]:
m, n = len(grid), len(grid[0])
num_layers = min(m, n) // 2
for layer in range(num_layers):
r1, c1 = layer, layer
r2, c2 = m - 1 - layer, n - 1 - layer
elements = []
for c in range(c1, c2):
elements.append(grid[r1][c])
for r in range(r1, r2):
elements.append(grid[r][c2])
for c in range(c2, c1, -1):
elements.append(grid[r2][c])
for r in range(r2, r1, -1):
elements.append(grid[r][c1])
length = len(elements)
shift = k % length
rotated = elements[shift:] + elements[:shift]
idx = 0
for c in range(c1, c2):
grid[r1][c] = rotated[idx]
idx += 1
for r in range(r1, r2):
grid[r][c2] = rotated[idx]
idx += 1
for c in range(c2, c1, -1):
grid[r2][c] = rotated[idx]
idx += 1
for r in range(r2, r1, -1):
grid[r][c1] = rotated[idx]
idx += 1
return grid
The extraction and placement loops mirror each other exactly. They walk the same path around the perimeter, just one reads and the other writes. This symmetry makes the code easier to verify: if your extraction order is correct, the placement order is automatically correct too.
Step 1: Identify layers. Start with the outermost layer.
The outer layer has 12 elements. The inner layer (2x2 center) has 4 elements.
Step 2: Extract the outer layer into a flat list.
Walk the perimeter: top row left-to-right, right column top-to-bottom, bottom row right-to-left, left column bottom-to-top.
Step 3: Rotate the outer layer by k = 1 (shift elements left by 1).
Each element moves one position counter-clockwise along the perimeter. Amber = before, green = after.
Step 4: Extract the inner layer and rotate it by k = 1.
The inner 2x2 layer is extracted and rotated the same way.
Step 5: Place rotated layers back into the grid.
Write the rotated elements back into their layer positions. The grid is now fully rotated.
The walkthrough above uses a 4x4 grid with k = 1. The outer layer has 12 elements, and the inner 2x2 layer has 4 elements. Each layer is extracted, rotated independently, and placed back.
Complexity analysis
| Metric | Value |
|---|---|
| Time | O(m * n) |
| Space | O(m * n) |
Time: each cell belongs to exactly one layer, and you visit it twice (once to extract, once to place back). The total work across all layers is proportional to m * n.
Space: the elements and rotated lists for each layer hold at most 2 * (m + n) - 4 elements. Across all layers the total storage is O(m * n). You could reduce this by rotating in place using reversal tricks, but the slice approach is cleaner and the space is bounded by the input size regardless.
The Building Blocks
This problem combines two reusable patterns that CodeBricks drills independently:
Layer perimeter traversal
The four-loop pattern for walking the boundary of a rectangular sub-grid:
for c in range(c1, c2): # top edge, left to right
process(grid[r1][c])
for r in range(r1, r2): # right edge, top to bottom
process(grid[r][c2])
for c in range(c2, c1, -1): # bottom edge, right to left
process(grid[r2][c])
for r in range(r2, r1, -1): # left edge, bottom to top
process(grid[r][c1])
This same traversal order powers Spiral Matrix (LeetCode 54), Rotate Image's layer approach (LeetCode 48), and Spiral Matrix II (LeetCode 59). Once you can write it from memory, any layer-based matrix problem becomes plugging in different per-element logic.
Cyclic array rotation
Rotating an array by k positions using slicing:
shift = k % len(arr)
rotated = arr[shift:] + arr[:shift]
This is the same technique used in Rotate Array (LeetCode 189). The modulo ensures you never do unnecessary full loops, and the slice cleanly splits and reassembles the array.
Edge cases to watch for
klarger than the perimeter: always computek % perimeter_lengthper layer. Akof 1000 on a layer with 12 elements is the same ask = 4.- Single-cell layer: if the innermost layer is a single cell (possible in non-square grids where one dimension is 2), it has a perimeter of 0 or 1. The rotation does nothing.
- Rectangular grids: the number of layers is
min(m, n) // 2, notm // 2orn // 2. A 2x6 grid has just one layer with 14 elements. - Different perimeter lengths per layer: each inner layer has a shorter perimeter, so the effective rotation changes even though
kis the same. Do not apply the same shift value across layers without re-computing the modulo.
From understanding to recall
You have read through the extraction, rotation, and placement steps. The four-loop perimeter walk makes sense. The slice-based rotation is clean. But can you write all of this from scratch when you are staring at a blank editor?
The perimeter traversal has a few details that are easy to mix up under pressure: the range directions, which boundary variable goes where, and the off-by-one between the four loops (notice how each loop stops one short so the next loop picks up the corner). The cyclic rotation needs the modulo step that is easy to forget when k is small in your test cases but large in the actual input.
Spaced repetition locks these details in. You write the four-loop extraction from memory, do the slice rotation, write the placement loops, and check your work. You do it today, again in two days, again in five. After a few reps, you see "cyclically rotate" and the entire layer-peel-rotate-place pattern flows out automatically.
Layer perimeter traversal and cyclic rotation are two of roughly 60 reusable building blocks that cover hundreds of LeetCode problems. Learning these individually and drilling them with spaced repetition is far more effective than grinding random problems and hoping the patterns stick.
Related posts
- Spiral Matrix - The foundational layer-by-layer traversal pattern that this problem builds on
- Rotate Image - In-place matrix rotation using the same boundary-peeling approach
- Spiral Matrix II - Writing values into a matrix in spiral order, the inverse of the extraction step used here
CodeBricks breaks Cyclically Rotating a Grid into its layer-traversal and cyclic-rotation building blocks, then drills them independently with spaced repetition. You type the four-loop perimeter walk and the slice rotation from scratch until the pattern is automatic. When a matrix layer problem shows up in your interview, you do not think about it. You just write it.