Facebook Pixel

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:

  1. The entire matrix rotates 90 degrees clockwise - This means the rightmost column becomes the top row, the leftmost column becomes the bottom row, etc.

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

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.

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

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:

  1. Obstacle '*': Clear the queue since stones above cannot fall past this point

    if ans[i][j] == '*':
        q.clear()
  2. Empty space '.': Add this position to the queue as a potential landing spot

    elif ans[i][j] == '.':
        q.append(i)
  3. Stone '#': If empty spaces exist below (queue not empty), move the stone to the lowest available position

    elif 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 position
  • append() 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 Evaluator

Example 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]
  • 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]
  • 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]
  • 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:

  1. 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 in O(m Γ— n) operations.

  2. Gravity simulation phase: After rotation, the algorithm processes the rotated matrix (which has dimensions n Γ— m). For each column j (total m columns), it iterates through all rows i (total n rows). Although there are deque operations (append, popleft, clear), each cell is visited once, and the deque operations are O(1) amortized. The total operations remain O(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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How many times is a tree node visited in a depth first search?


Recommended Readings

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

Load More