1861. Rotating the Box
Problem Description
You have a box represented as an m x n
matrix called boxGrid
, where each cell contains one of three possible characters:
'#'
represents a stone'*'
represents a stationary obstacle that cannot move'.'
represents an empty space
The problem asks you to simulate what happens when this box is rotated 90 degrees clockwise. When the box rotates, two things happen:
-
The entire matrix rotates 90 degrees clockwise - This means the rightmost column becomes the top row, the leftmost column becomes the bottom row, etc.
-
Stones fall due to gravity - After rotation, gravity causes stones to fall downward in their new orientation. Each stone will fall until it hits:
- An obstacle (
'*'
) - Another stone (
'#'
) - The bottom of the box
- An obstacle (
Important constraints:
- Obstacles (
'*'
) remain fixed in their positions and don't fall - Stones only fall vertically downward in the rotated box (they don't move horizontally)
- Initially, all stones are guaranteed to be resting on something (not floating)
The task is to return the final state of the box as an n x m
matrix (note the dimensions are swapped due to the 90-degree rotation) showing where all stones end up after the rotation and falling process is complete.
For example, if you have a stone above an empty space in the rotated box, it will fall through the empty space until it hits an obstacle, another stone, or reaches the bottom. The simulation needs to handle multiple stones in the same column falling and stacking on top of each other correctly.
Intuition
The problem can be broken down into two independent operations: rotating the matrix and simulating gravity.
For the rotation part, we need to understand how a 90-degree clockwise rotation transforms coordinates. When rotating a matrix clockwise, the element at position (i, j)
in the original matrix moves to position (j, m-1-i)
in the rotated matrix, where m
is the number of rows. This is a standard matrix transformation - the row index becomes the column index, and the column index determines the new row position from the bottom.
For simulating gravity, we need to think about how stones fall in each column of the rotated matrix. The key insight is that we can process each column independently from bottom to top. As we scan upward in a column, we need to track where stones can potentially fall to.
Using a queue to track empty positions is clever because:
- When we encounter an empty space
'.'
, we add its position to our queue of available spots - When we encounter a stone
'#'
, if there are empty spots below it (in our queue), the stone should fall to the lowest available spot - When we encounter an obstacle
'*'
, it blocks everything above it from falling past that point, so we clear our queue
Processing from bottom to top ensures that we fill the lowest positions first, naturally simulating how multiple stones would stack on top of each other. The queue automatically maintains the order of available empty positions from bottom to top, making it easy to always place falling stones in the correct spot.
The beauty of this approach is that it separates the geometric transformation (rotation) from the physics simulation (gravity), making each part simpler to implement and understand.
Learn more about Two Pointers patterns.
Solution Approach
The solution implements the two-phase approach: first rotating the matrix, then simulating gravity.
Phase 1: Matrix Rotation (90 degrees clockwise)
We create a new matrix ans
with dimensions n x m
(swapped from the original m x n
). For each element at position (i, j)
in the original matrix, we place it at position (j, m-1-i)
in the rotated matrix:
ans = [[None] * m for _ in range(n)]
for i in range(m):
for j in range(n):
ans[j][m - i - 1] = box[i][j]
This transformation ensures that:
- The first row becomes the last column
- The last row becomes the first column
- Elements rotate 90 degrees clockwise
Phase 2: Simulating Gravity with Queue
After rotation, we process each column independently to simulate stones falling:
for j in range(m): # For each column
q = deque() # Queue to track empty positions
for i in range(n - 1, -1, -1): # Scan from bottom to top
For each position in the column (scanning upward), we handle three cases:
-
Obstacle
'*'
: Clear the queue since stones above cannot fall past this pointif ans[i][j] == '*': q.clear()
-
Empty space
'.'
: Add this position to the queue as a potential landing spotelif ans[i][j] == '.': q.append(i)
-
Stone
'#'
: If empty spaces exist below (queue not empty), move the stone to the lowest available positionelif q: ans[q.popleft()][j] = '#' # Place stone at lowest empty spot ans[i][j] = '.' # Current position becomes empty q.append(i) # Add current position as new empty spot
The queue (deque
) is used efficiently here:
popleft()
always gives us the lowest (earliest added) empty positionappend()
adds newly emptied positions to the back- This maintains the bottom-to-top order of available positions
The algorithm correctly handles multiple stones falling in sequence because when a stone falls from position i
, that position becomes empty and is added back to the queue, allowing stones above to potentially fall into it.
After processing all columns, ans
contains the final state of the box after rotation and gravity simulation.
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 work through a small example to illustrate the solution approach.
Initial Box (3x3):
# . * # # . . . .
Step 1: Rotate 90 degrees clockwise
Using the transformation formula (i,j) β (j, m-1-i)
where m=3:
(0,0)='#'
β(0,2)='#'
(0,1)='.'
β(1,2)='.'
(0,2)='*'
β(2,2)='*'
(1,0)='#'
β(0,1)='#'
(1,1)='#'
β(1,1)='#'
(1,2)='.'
β(2,1)='.'
(2,0)='.'
β(0,0)='.'
(2,1)='.'
β(1,0)='.'
(2,2)='.'
β(2,0)='.'
After rotation:
. # # . # . . . *
Step 2: Apply gravity column by column
Column 0: Process from bottom to top (indices 2β1β0)
- i=2:
'.'
- add to queue:q=[2]
- i=1:
'.'
- add to queue:q=[2,1]
- i=0:
'.'
- add to queue:q=[2,1,0]
- Final column 0:
['.', '.', '.']
(no stones to fall)
Column 1: Process from bottom to top
- i=2:
'.'
- add to queue:q=[2]
- i=1:
'#'
- stone with empty space below!- Place stone at
q.popleft()=2
: position (2,1) becomes'#'
- Current position (1,1) becomes
'.'
- Add position 1 to queue:
q=[1]
- Place stone at
- i=0:
'#'
- stone with empty space below!- Place stone at
q.popleft()=1
: position (1,1) becomes'#'
- Current position (0,1) becomes
'.'
- Add position 0 to queue:
q=[0]
- Place stone at
- Final column 1:
['.', '#', '#']
Column 2: Process from bottom to top
- i=2:
'*'
- obstacle, clear queue:q=[]
- i=1:
'.'
- add to queue:q=[1]
- i=0:
'#'
- stone with empty space below!- Place stone at
q.popleft()=1
: position (1,2) becomes'#'
- Current position (0,2) becomes
'.'
- Add position 0 to queue:
q=[0]
- Place stone at
- Final column 2:
['.', '#', '*']
Final Result:
. . . . # # . # *
The stones that were originally in the top-left and middle of the box have fallen to rest on the bottom row and the obstacle after rotation.
Solution Implementation
1from collections import deque
2from typing import List
3
4class Solution:
5 def rotateTheBox(self, box: List[List[str]]) -> List[List[str]]:
6 # Get dimensions of the original box
7 rows, cols = len(box), len(box[0])
8
9 # Create rotated box with swapped dimensions
10 # After 90Β° clockwise rotation: new_rows = original_cols, new_cols = original_rows
11 rotated_box = [[None] * rows for _ in range(cols)]
12
13 # Perform 90 degree clockwise rotation
14 # Original position (i, j) maps to (j, rows - 1 - i) after rotation
15 for row in range(rows):
16 for col in range(cols):
17 rotated_box[col][rows - row - 1] = box[row][col]
18
19 # Apply gravity to make stones fall down in the rotated box
20 # Process each column independently
21 for col in range(rows):
22 # Queue to track available empty positions where stones can fall
23 empty_positions = deque()
24
25 # Process from bottom to top (gravity pulls stones down)
26 for row in range(cols - 1, -1, -1):
27 if rotated_box[row][col] == '*':
28 # Obstacle found - clear empty positions queue
29 # Stones cannot fall past obstacles
30 empty_positions.clear()
31 elif rotated_box[row][col] == '.':
32 # Empty space found - add to available positions
33 empty_positions.append(row)
34 elif rotated_box[row][col] == '#' and empty_positions:
35 # Stone found with empty space below - make it fall
36 # Move stone to the lowest available empty position
37 lowest_empty = empty_positions.popleft()
38 rotated_box[lowest_empty][col] = '#'
39 rotated_box[row][col] = '.'
40 # Current position becomes empty after stone falls
41 empty_positions.append(row)
42
43 return rotated_box
44
1class Solution {
2 public char[][] rotateTheBox(char[][] box) {
3 int rows = box.length;
4 int cols = box[0].length;
5
6 // Create result matrix with dimensions swapped (rotated 90 degrees clockwise)
7 char[][] result = new char[cols][rows];
8
9 // Step 1: Rotate the box 90 degrees clockwise
10 // Original position [i][j] maps to [j][rows - 1 - i] after rotation
11 for (int i = 0; i < rows; i++) {
12 for (int j = 0; j < cols; j++) {
13 result[j][rows - 1 - i] = box[i][j];
14 }
15 }
16
17 // Step 2: Apply gravity to make stones fall down in each column
18 for (int col = 0; col < rows; col++) {
19 // Queue to track available empty positions where stones can fall
20 Deque<Integer> emptyPositions = new ArrayDeque<>();
21
22 // Process from bottom to top of each column
23 for (int row = cols - 1; row >= 0; row--) {
24 if (result[row][col] == '*') {
25 // Obstacle found - clear all empty positions above it
26 emptyPositions.clear();
27 } else if (result[row][col] == '.') {
28 // Empty space found - add to available positions
29 emptyPositions.offer(row);
30 } else if (result[row][col] == '#' && !emptyPositions.isEmpty()) {
31 // Stone found with empty space below - make it fall
32 int lowestEmptyPosition = emptyPositions.pollFirst();
33 result[lowestEmptyPosition][col] = '#';
34 result[row][col] = '.';
35 // Current position becomes empty after stone falls
36 emptyPositions.offer(row);
37 }
38 }
39 }
40
41 return result;
42 }
43}
44
1class Solution {
2public:
3 vector<vector<char>> rotateTheBox(vector<vector<char>>& box) {
4 int rows = box.size();
5 int cols = box[0].size();
6
7 // Create result matrix with dimensions swapped (rotated 90 degrees clockwise)
8 vector<vector<char>> rotatedBox(cols, vector<char>(rows));
9
10 // Step 1: Rotate the box 90 degrees clockwise
11 // Original position (i, j) maps to (j, rows - 1 - i) after rotation
12 for (int row = 0; row < rows; ++row) {
13 for (int col = 0; col < cols; ++col) {
14 rotatedBox[col][rows - row - 1] = box[row][col];
15 }
16 }
17
18 // Step 2: Apply gravity - make stones fall down in each column
19 for (int col = 0; col < rows; ++col) {
20 queue<int> emptyPositions; // Track empty positions where stones can fall
21
22 // Process from bottom to top (gravity pulls stones down)
23 for (int row = cols - 1; row >= 0; --row) {
24 if (rotatedBox[row][col] == '*') {
25 // Obstacle found - clear all empty positions above it
26 queue<int> newQueue;
27 swap(emptyPositions, newQueue);
28 }
29 else if (rotatedBox[row][col] == '.') {
30 // Empty space - add to available positions
31 emptyPositions.push(row);
32 }
33 else if (rotatedBox[row][col] == '#' && !emptyPositions.empty()) {
34 // Stone found and there's an empty position below
35 // Move stone to the lowest available empty position
36 int targetRow = emptyPositions.front();
37 emptyPositions.pop();
38
39 rotatedBox[targetRow][col] = '#'; // Place stone at target
40 rotatedBox[row][col] = '.'; // Current position becomes empty
41 emptyPositions.push(row); // Add current position as available
42 }
43 }
44 }
45
46 return rotatedBox;
47 }
48};
49
1function rotateTheBox(box: string[][]): string[][] {
2 const rows = box.length;
3 const cols = box[0].length;
4
5 // Create result matrix with dimensions swapped (rotated 90 degrees clockwise)
6 const rotatedBox: string[][] = Array(cols).fill(null).map(() => Array(rows).fill(''));
7
8 // Step 1: Rotate the box 90 degrees clockwise
9 // Original position (i, j) maps to (j, rows - 1 - i) after rotation
10 for (let row = 0; row < rows; row++) {
11 for (let col = 0; col < cols; col++) {
12 rotatedBox[col][rows - row - 1] = box[row][col];
13 }
14 }
15
16 // Step 2: Apply gravity - make stones fall down in each column
17 for (let col = 0; col < rows; col++) {
18 const emptyPositions: number[] = []; // Track empty positions where stones can fall
19
20 // Process from bottom to top (gravity pulls stones down)
21 for (let row = cols - 1; row >= 0; row--) {
22 if (rotatedBox[row][col] === '*') {
23 // Obstacle found - clear all empty positions above it
24 emptyPositions.length = 0;
25 }
26 else if (rotatedBox[row][col] === '.') {
27 // Empty space - add to available positions
28 emptyPositions.push(row);
29 }
30 else if (rotatedBox[row][col] === '#' && emptyPositions.length > 0) {
31 // Stone found and there's an empty position below
32 // Move stone to the lowest available empty position
33 const targetRow = emptyPositions.shift()!;
34
35 rotatedBox[targetRow][col] = '#'; // Place stone at target
36 rotatedBox[row][col] = '.'; // Current position becomes empty
37 emptyPositions.push(row); // Add current position as available
38 }
39 }
40 }
41
42 return rotatedBox;
43}
44
Time and Space Complexity
Time Complexity: O(m Γ n)
The algorithm consists of two main phases:
-
Matrix rotation phase: The nested loops iterate through all
m Γ n
elements of the original box to create the rotated matrix. Each element is accessed and assigned exactly once, resulting inO(m Γ n)
operations. -
Gravity simulation phase: After rotation, the algorithm processes the rotated matrix (which has dimensions
n Γ m
). For each columnj
(totalm
columns), it iterates through all rowsi
(totaln
rows). Although there are deque operations (append
,popleft
,clear
), each cell is visited once, and the deque operations areO(1)
amortized. The total operations remainO(n Γ m) = O(m Γ n)
.
Since both phases are O(m Γ n)
and execute sequentially, the overall time complexity is O(m Γ n) + O(m Γ n) = O(m Γ n)
.
Space Complexity: O(m Γ n)
The algorithm allocates a new 2D array ans
with dimensions n Γ m
to store the rotated box. This requires O(n Γ m) = O(m Γ n)
space. The deque q
uses at most O(n)
space in the worst case (when a column contains all empty cells), but this is dominated by the space used for the ans
matrix. Therefore, the total space complexity is O(m Γ n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Rotation Mapping
Issue: One of the most common mistakes is getting the rotation transformation wrong. Many developers incorrectly map (i, j)
to (j, i)
or (n-1-j, i)
, which doesn't produce a proper 90-degree clockwise rotation.
Why it happens: The formula for 90-degree clockwise rotation isn't intuitive, and it's easy to confuse with transpose or other transformations.
Solution: Remember that for 90-degree clockwise rotation from an mΓn
matrix to an nΓm
matrix, the correct mapping is:
- Original position
(i, j)
β Rotated position(j, m-1-i)
- Verify with a simple example: the top-left corner
(0, 0)
should map to the top-right(0, m-1)
Pitfall 2: Processing Gravity in Wrong Direction
Issue: Processing stones from top to bottom instead of bottom to top when simulating gravity, leading to incorrect stone positions.
Why it happens: It seems logical to process from top since stones fall down, but this approach makes it difficult to determine where each stone should land.
Solution: Always process from bottom to top when simulating gravity:
for row in range(cols - 1, -1, -1): # Bottom to top
This way, you know exactly which positions are available for falling stones before encountering them.
Pitfall 3: Not Maintaining Order of Empty Positions
Issue: Using a stack (LIFO) instead of a queue (FIFO) for tracking empty positions, causing stones to fall to incorrect positions.
Why it happens: Both data structures seem reasonable for tracking positions, but they produce different results.
Solution: Use a deque with popleft()
to ensure stones fall to the lowest available position:
empty_positions = deque() lowest_empty = empty_positions.popleft() # Gets the earliest (lowest) position
Pitfall 4: Forgetting to Clear Queue at Obstacles
Issue: Not clearing the empty positions queue when encountering an obstacle, allowing stones to incorrectly "fall through" obstacles.
Why it happens: It's easy to focus on moving stones and forget that obstacles create barriers that reset the falling behavior.
Solution: Always clear the queue when hitting an obstacle:
if rotated_box[row][col] == '*': empty_positions.clear()
Pitfall 5: Dimension Confusion After Rotation
Issue: Using the wrong dimensions for the rotated matrix or incorrectly iterating through rows and columns after rotation.
Why it happens: After rotation, the dimensions swap (mΓn
becomes nΓm
), and it's easy to mix up which dimension is which.
Solution: Clearly define and use consistent variable names:
rows, cols = len(box), len(box[0]) # Original dimensions
rotated_box = [[None] * rows for _ in range(cols)] # Swapped dimensions
# After rotation: iterate cols for rows, rows for columns
for col in range(rows): # Each column in rotated box
for row in range(cols - 1, -1, -1): # Each row in that column
How many times is a tree node visited in a depth first search?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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!