1861. Rotating the Box - Explanation

Problem Link

Description

You are given an m x n matrix of characters boxGrid representing a side-view of a box. Each cell of the box is one of the following:

  • A stone '#'
  • A stationary obstacle '*'
  • Empty '.'

The box is rotated 90 degrees clockwise, causing some of the stones to fall due to gravity. Each stone falls down until it lands on an obstacle, another stone, or the bottom of the box. Gravity does not affect the obstacles' positions, and the inertia from the box's rotation does not affect the stones' horizontal positions.

It is guaranteed that each stone in boxGrid rests on an obstacle, another stone, or the bottom of the box.

Return an n x m matrix representing the box after the rotation described above.

Example 1:

Input: boxGrid = [
    ["#",".","*","."],
    ["#","#","*","."]
]

Output: [
    ["#","."],
    ["#","#"],
    ["*","*"],
    [".","."]
]

Example 2:

Input: boxGrid = [
    ["#","#","*",".","*","."],
    ["#","#","#","*",".","."],
    ["#","#","#",".","#","."]
]

Output: [
    [".","#","#"],
    [".","#","#"],
    ["#","#","*"],
    ["#","*","."],
    ["#",".","*"],
    ["#",".","."]
]

Constraints:

  • m == boxGrid.length
  • n == boxGrid[i].length
  • 1 <= m, n <= 500
  • boxGrid[i][j] is either '#', '*', or '.'.


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • 2D Array Manipulation - Working with grids, understanding row and column indexing
  • Two Pointers Technique - Using a pointer to track the next available position while iterating through elements
  • Gravity Simulation - Moving elements (stones) toward a direction until they hit an obstacle or boundary
  • Matrix Rotation - Transforming coordinates from original position (r,c) to rotated position for 90-degree clockwise rotation

1. Brute Force

Intuition

When the box is rotated 90 degrees clockwise, gravity pulls stones downward in the new orientation. Before rotation, rows become columns, so stones in each row should fall to the right (toward the end of the row). We simulate gravity by moving each stone as far right as possible until it hits another stone, an obstacle, or the boundary. After simulating gravity, we perform the rotation by transposing and reversing the row order.

Algorithm

  1. For each row, iterate from right to left.
  2. When a stone # is found, scan rightward to find the farthest empty cell . before hitting an obstacle * or boundary.
  3. Move the stone to that position.
  4. After processing all rows, create the rotated grid:
    • The new grid has COLS rows and ROWS columns.
    • For each column c in the original grid, the new row at index c contains elements from bottom to top of that column.
  5. Return the rotated grid.
class Solution:
    def rotateTheBox(self, boxGrid: List[List[str]]) -> List[List[str]]:
        ROWS, COLS = len(boxGrid), len(boxGrid[0])

        for r in range(ROWS - 1, -1, -1):
            for c1 in range(COLS - 1, -1, -1):
                if boxGrid[r][c1] == '#':
                    c2 = c1 + 1
                    while c2 < COLS and boxGrid[r][c2] == '.':
                        c2 += 1

                    boxGrid[r][c1] = '.'
                    boxGrid[r][c2 - 1] = '#'


        res = []
        for c in range(COLS):
            col = []
            for r in range(ROWS - 1, -1, -1):
                col.append(boxGrid[r][c])
            res.append(col)
        return res

Time & Space Complexity

  • Time complexity: O(mn2)O(m * n ^ 2)
  • Space complexity: O(mn)O(m * n)

Where mm is the number of rows and nn is the number of columns.


2. Two Pointers - I

Intuition

Instead of scanning rightward for each stone, we can use a two-pointer technique. Maintain a pointer i that tracks the rightmost available position for a stone. Scan from right to left: when we see a stone, swap it with position i and decrement i. When we see an obstacle, reset i to just before the obstacle. This avoids redundant scanning and processes each cell at most twice.

Algorithm

  1. For each row, initialize pointer i to COLS - 1 (rightmost position).
  2. Iterate from right to left through the row:
    • If the cell is a stone #, swap it with position i, then decrement i.
    • If the cell is an obstacle *, reset i to c - 1 (just before the obstacle).
  3. After processing all rows, construct the rotated grid by mapping each column of the original to a row in the result (reading bottom to top).
  4. Return the rotated grid.
class Solution:
    def rotateTheBox(self, boxGrid: List[List[str]]) -> List[List[str]]:
        ROWS, COLS = len(boxGrid), len(boxGrid[0])

        for r in range(ROWS):
            i = COLS - 1
            for c in reversed(range(COLS)):
                if boxGrid[r][c] == "#":
                    boxGrid[r][c], boxGrid[r][i] = boxGrid[r][i], boxGrid[r][c]
                    i -= 1
                elif boxGrid[r][c] == "*":
                    i = c - 1

        res = []
        for c in range(COLS):
            col = []  # this is a row after rotation
            for r in reversed(range(ROWS)):
                col.append(boxGrid[r][c])
            res.append(col)

        return res

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(mn)O(m * n)

Where mm is the number of rows and nn is the number of columns.


3. Two Pointers - II

Intuition

We can combine the gravity simulation and rotation into a single pass. Instead of modifying the original grid and then rotating, we directly write stones and obstacles to their final positions in the rotated result grid. This eliminates the need to modify the input and performs both operations simultaneously.

Algorithm

  1. Create a result grid of size COLS x ROWS, filled with empty cells ..
  2. For each row in the original grid, initialize pointer i to COLS - 1.
  3. Iterate from right to left:
    • If the cell is a stone #, place it at the rotated position corresponding to i, then decrement i.
    • If the cell is an obstacle *, place it at its rotated position and reset i to c - 1.
  4. The rotated position for original (r, c) is (c, ROWS - r - 1) for obstacles, and (i, ROWS - r - 1) for stones.
  5. Return the result grid.
class Solution:
    def rotateTheBox(self, boxGrid: List[List[str]]) -> List[List[str]]:
        ROWS, COLS = len(boxGrid), len(boxGrid[0])

        res = [["."] * ROWS for _ in range(COLS)]

        for r in range(ROWS):
            i = COLS - 1
            for c in reversed(range(COLS)):
                if boxGrid[r][c] == "#":
                    res[i][ROWS - r - 1] = "#"
                    i -= 1
                elif boxGrid[r][c] == "*":
                    res[c][ROWS - r - 1] = "*"
                    i = c - 1

        return res

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(mn)O(m * n)

Where mm is the number of rows and nn is the number of columns.


Common Pitfalls

Applying Gravity in the Wrong Direction

After rotation, gravity pulls stones downward, but before rotation stones should fall to the right (toward higher column indices). Processing stones left-to-right instead of right-to-left causes stones to fall incorrectly, as earlier stones block the path for later ones.

Forgetting to Reset the Drop Position After Obstacles

When an obstacle * is encountered, the drop position pointer must reset to just before the obstacle (c - 1). Failing to reset this pointer causes stones to incorrectly pass through or stack on top of obstacles.

Incorrect Rotation Index Mapping

The rotation transforms position (r, c) to (c, ROWS - 1 - r) for a 90-degree clockwise rotation. Using incorrect formulas like (c, r) or (ROWS - 1 - r, c) results in a transposed or incorrectly flipped matrix instead of a proper rotation.