Facebook Pixel

1706. Where Will the Ball Fall

MediumArrayMatrixSimulation
Leetcode Link

Problem Description

You have a 2D grid of size m x n that represents a box with an open top and bottom. You need to drop n balls from the top, one ball from each column, and determine where each ball exits at the bottom (or if it gets stuck).

Each cell in the grid contains a diagonal board that redirects balls:

  • A value of 1 represents a board spanning from top-left to bottom-right (redirects ball to the right)
  • A value of -1 represents a board spanning from top-right to bottom-left (redirects ball to the left)

A ball gets stuck in two scenarios:

  1. V-shaped pattern: When two adjacent cells form a "V" shape - this happens when a cell with value 1 (redirects right) is next to a cell with value -1 (redirects left)
  2. Wall collision: When a board redirects the ball into either the left or right wall of the box

The ball movement works as follows:

  • When a ball encounters a cell with value 1, it moves diagonally down-right to the next row and next column
  • When a ball encounters a cell with value -1, it moves diagonally down-left to the next row and previous column
  • If the ball successfully passes through all rows without getting stuck, it falls out of the bottom

Your task is to return an array answer of size n where answer[i] represents:

  • The column index where the ball dropped from column i exits at the bottom, or
  • -1 if the ball gets stuck somewhere in the box

For example, if you drop a ball from column 2 and it exits from column 3 at the bottom, then answer[2] = 3. If the ball gets stuck, then answer[2] = -1.

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

Intuition

The key insight is to simulate the path of each ball as it falls through the grid. We can think of this as tracing the ball's journey from top to bottom, following the diagonal boards at each step.

When we drop a ball at column j, it encounters a series of diagonal boards as it falls. At each cell (i, j), the ball needs to move to the next row based on the board's direction. If the board value is 1, the ball tries to move to (i+1, j+1). If the board value is -1, the ball tries to move to (i+1, j-1).

The critical observation is identifying when a ball gets stuck. Let's visualize what happens:

  • If we're at the leftmost column (j = 0) and the board directs left (grid[i][j] = -1), the ball hits the left wall
  • If we're at the rightmost column (j = n-1) and the board directs right (grid[i][j] = 1), the ball hits the right wall
  • The "V" pattern occurs when adjacent cells have opposite directions. If grid[i][j] = 1 (directing right) and grid[i][j+1] = -1 (directing left), the ball gets trapped between them
  • Similarly, if grid[i][j] = -1 (directing left) and grid[i][j-1] = 1 (directing right), the ball is trapped

This naturally leads to a recursive approach: for each ball starting at column j, we can use depth-first search (DFS) to trace its path. At each step, we check if the ball gets stuck according to the conditions above. If not, we recursively move to the next position. When the ball reaches row m (past the last row), we know it has successfully fallen through and can return its final column position.

The beauty of this approach is that each ball's path is independent, so we can simply run the DFS simulation for each starting column and collect all the results.

Solution Approach

We implement the solution using Depth-First Search (DFS) to simulate each ball's path through the grid.

DFS Function Design: We create a recursive function dfs(i, j) that returns the exit column for a ball currently at position (i, j). The function returns -1 if the ball gets stuck.

Base Case: When i == m (the ball has passed through all rows), we return the current column j as the exit position.

Checking for Stuck Conditions: Before moving the ball, we check four conditions where the ball gets stuck:

  1. Left wall collision: j == 0 and grid[i][j] == -1

    • Ball is at the leftmost column and the board directs it left into the wall
  2. Right wall collision: j == n-1 and grid[i][j] == 1

    • Ball is at the rightmost column and the board directs it right into the wall
  3. V-pattern (right-left): grid[i][j] == 1 and grid[i][j+1] == -1

    • Current cell directs right while the adjacent right cell directs left, forming a "V"
  4. V-pattern (left-right): grid[i][j] == -1 and grid[i][j-1] == 1

    • Current cell directs left while the adjacent left cell directs right, forming a "V"

If any of these conditions are met, we immediately return -1.

Recursive Movement: If the ball doesn't get stuck, we move it to the next row:

  • If grid[i][j] == 1: Move diagonally down-right by calling dfs(i+1, j+1)
  • If grid[i][j] == -1: Move diagonally down-left by calling dfs(i+1, j-1)

Main Function: We initialize the grid dimensions m and n, then use list comprehension to call dfs(0, j) for each starting column j from 0 to n-1. This gives us the final position (or -1) for each ball dropped from the top.

The time complexity is O(m × n) as each ball can potentially traverse through all rows, and we have n balls. The space complexity is O(m) for the recursion stack depth in the worst case.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Consider this 3×3 grid:

grid = [[1,  1, -1],
        [1, -1,  1],
        [1,  1,  1]]

Let's trace what happens when we drop balls from each column:

Ball from Column 0:

  • Start at position (0, 0), value is 1 (directs right)
  • Check stuck conditions:
    • Not at right wall (j=0, not n-1)
    • Check right neighbor: grid[0][1] = 1 (not -1), so no V-pattern
  • Move diagonally down-right to (1, 1)
  • At (1, 1), value is -1 (directs left)
  • Check stuck conditions:
    • Not at left wall (j=1, not 0)
    • Check left neighbor: grid[1][0] = 1, forms V-pattern!
    • Ball gets stuck
  • Return -1

Ball from Column 1:

  • Start at position (0, 1), value is 1 (directs right)
  • Check stuck conditions:
    • Not at right wall (j=1, not n-1)
    • Check right neighbor: grid[0][2] = -1, forms V-pattern!
    • Ball gets stuck
  • Return -1

Ball from Column 2:

  • Start at position (0, 2), value is -1 (directs left)
  • Check stuck conditions:
    • Not at left wall (j=2, not 0)
    • Check left neighbor: grid[0][1] = 1, forms V-pattern!
    • Ball gets stuck
  • Return -1

Final result: [-1, -1, -1] - all balls get stuck!

Let's try another example where some balls make it through:

grid = [[1,  1,  1],
        [1,  1,  1]]

Ball from Column 0:

  • Start at (0, 0), value is 1 (directs right)
  • No stuck conditions (grid[0][1] = 1, same direction)
  • Move to (1, 1)
  • At (1, 1), value is 1 (directs right)
  • No stuck conditions (grid[1][2] = 1, same direction)
  • Move to (2, 2) - past the last row!
  • Return 2 (exit column)

Ball from Column 1:

  • Start at (0, 1), value is 1 (directs right)
  • No stuck conditions (grid[0][2] = 1, same direction)
  • Move to (1, 2)
  • At (1, 2), value is 1 (directs right)
  • Stuck condition: at right wall (j=2 is n-1) and directing right
  • Return -1

Ball from Column 2:

  • Start at (0, 2), value is 1 (directs right)
  • Stuck condition: at right wall (j=2 is n-1) and directing right
  • Return -1

Final result: [2, -1, -1]

This walkthrough demonstrates how the DFS function checks for stuck conditions at each step and recursively traces the ball's path until it either exits or gets stuck.

Solution Implementation

1class Solution:
2    def findBall(self, grid: List[List[int]]) -> List[int]:
3        """
4        Simulate dropping balls through a grid with diagonal boards.
5        Each cell contains 1 (redirects right) or -1 (redirects left).
6        Returns final column position for each ball, or -1 if stuck.
7        """
8      
9        def simulate_ball_drop(row: int, col: int) -> int:
10            """
11            Recursively simulate a ball dropping from position (row, col).
12          
13            Args:
14                row: Current row position of the ball
15                col: Current column position of the ball
16              
17            Returns:
18                Final column position if ball exits bottom, -1 if stuck
19            """
20            # Ball successfully reached the bottom of the grid
21            if row == num_rows:
22                return col
23          
24            # Check if ball hits left wall while being directed left
25            if col == 0 and grid[row][col] == -1:
26                return -1
27          
28            # Check if ball hits right wall while being directed right
29            if col == num_cols - 1 and grid[row][col] == 1:
30                return -1
31          
32            # Check for V-shaped trap: current cell directs right, next cell directs left
33            if grid[row][col] == 1 and grid[row][col + 1] == -1:
34                return -1
35          
36            # Check for V-shaped trap: current cell directs left, previous cell directs right
37            if grid[row][col] == -1 and grid[row][col - 1] == 1:
38                return -1
39          
40            # Ball continues to next row
41            # If current cell is 1, ball moves right; if -1, ball moves left
42            if grid[row][col] == 1:
43                return simulate_ball_drop(row + 1, col + 1)
44            else:
45                return simulate_ball_drop(row + 1, col - 1)
46      
47        # Initialize grid dimensions
48        num_rows = len(grid)
49        num_cols = len(grid[0])
50      
51        # Simulate dropping a ball from each column at the top
52        result = []
53        for starting_col in range(num_cols):
54            final_position = simulate_ball_drop(0, starting_col)
55            result.append(final_position)
56      
57        return result
58
1class Solution {
2    // Grid dimensions
3    private int numRows;
4    private int numCols;
5    private int[][] grid;
6
7    /**
8     * Simulates dropping balls through a grid with diagonal boards.
9     * Each cell contains 1 (redirect right) or -1 (redirect left).
10     * Returns final column position for each ball, or -1 if stuck.
11     * 
12     * @param grid The board configuration
13     * @return Array where ans[i] is the exit column for ball dropped at column i
14     */
15    public int[] findBall(int[][] grid) {
16        // Initialize grid dimensions and store grid reference
17        this.numRows = grid.length;
18        this.numCols = grid[0].length;
19        this.grid = grid;
20      
21        // Result array to store final position of each ball
22        int[] result = new int[numCols];
23      
24        // Simulate dropping a ball from each column
25        for (int col = 0; col < numCols; col++) {
26            result[col] = dfs(0, col);
27        }
28      
29        return result;
30    }
31
32    /**
33     * Recursively traces the path of a ball through the grid.
34     * 
35     * @param row Current row position of the ball
36     * @param col Current column position of the ball
37     * @return Final column position if ball exits, -1 if stuck
38     */
39    private int dfs(int row, int col) {
40        // Base case: ball has successfully passed through all rows
41        if (row == numRows) {
42            return col;
43        }
44      
45        // Check if ball gets stuck at left wall
46        // Ball hits left wall when trying to move left from leftmost column
47        if (col == 0 && grid[row][col] == -1) {
48            return -1;
49        }
50      
51        // Check if ball gets stuck at right wall
52        // Ball hits right wall when trying to move right from rightmost column
53        if (col == numCols - 1 && grid[row][col] == 1) {
54            return -1;
55        }
56      
57        // Check for V-shaped trap (right-left pattern)
58        // Ball gets stuck when current cell redirects right but next cell redirects left
59        if (grid[row][col] == 1 && grid[row][col + 1] == -1) {
60            return -1;
61        }
62      
63        // Check for V-shaped trap (left-right pattern)
64        // Ball gets stuck when current cell redirects left but previous cell redirects right
65        if (grid[row][col] == -1 && grid[row][col - 1] == 1) {
66            return -1;
67        }
68      
69        // Move ball to next row based on current cell's direction
70        // If current cell is 1, move right; if -1, move left
71        return grid[row][col] == 1 
72            ? dfs(row + 1, col + 1)  // Move down-right
73            : dfs(row + 1, col - 1); // Move down-left
74    }
75}
76
1class Solution {
2public:
3    vector<int> findBall(vector<vector<int>>& grid) {
4        int numRows = grid.size();
5        int numCols = grid[0].size();
6        vector<int> result(numCols);
7      
8        // Define a recursive function to simulate ball movement
9        // Parameters: currentRow - current row position, currentCol - current column position
10        // Returns: the column where ball exits from bottom, or -1 if ball gets stuck
11        function<int(int, int)> simulateBallDrop = [&](int currentRow, int currentCol) {
12            // Ball successfully reached the bottom of the grid
13            if (currentRow == numRows) {
14                return currentCol;
15            }
16          
17            // Check if ball gets stuck at left wall
18            // Ball hits left wall when it's at column 0 and the slope directs it left (-1)
19            if (currentCol == 0 && grid[currentRow][currentCol] == -1) {
20                return -1;
21            }
22          
23            // Check if ball gets stuck at right wall
24            // Ball hits right wall when it's at last column and the slope directs it right (1)
25            if (currentCol == numCols - 1 && grid[currentRow][currentCol] == 1) {
26                return -1;
27            }
28          
29            // Check for V-shaped trap (right slope followed by left slope)
30            // Ball gets stuck when current cell directs right (1) and next cell directs left (-1)
31            if (grid[currentRow][currentCol] == 1 && grid[currentRow][currentCol + 1] == -1) {
32                return -1;
33            }
34          
35            // Check for V-shaped trap (left slope followed by right slope)
36            // Ball gets stuck when current cell directs left (-1) and previous cell directs right (1)
37            if (grid[currentRow][currentCol] == -1 && grid[currentRow][currentCol - 1] == 1) {
38                return -1;
39            }
40          
41            // Move the ball to next row based on current cell's direction
42            // If current cell is 1 (right slope), ball moves to bottom-right
43            // If current cell is -1 (left slope), ball moves to bottom-left
44            return grid[currentRow][currentCol] == 1 ? 
45                   simulateBallDrop(currentRow + 1, currentCol + 1) : 
46                   simulateBallDrop(currentRow + 1, currentCol - 1);
47        };
48      
49        // Simulate dropping a ball from each column at the top
50        for (int col = 0; col < numCols; ++col) {
51            result[col] = simulateBallDrop(0, col);
52        }
53      
54        return result;
55    }
56};
57
1/**
2 * Simulates balls falling through a grid with diagonal boards
3 * Each cell contains 1 (redirects right) or -1 (redirects left)
4 * Returns the exit column for each ball, or -1 if stuck
5 * @param grid - 2D array where 1 redirects right, -1 redirects left
6 * @returns Array indicating exit column for each ball starting position
7 */
8function findBall(grid: number[][]): number[] {
9    const rowCount: number = grid.length;
10    const columnCount: number = grid[0].length;
11  
12    /**
13     * Recursively traces the path of a ball through the grid
14     * @param currentRow - Current row position of the ball
15     * @param currentColumn - Current column position of the ball
16     * @returns Exit column if ball reaches bottom, -1 if stuck
17     */
18    const traceBallPath = (currentRow: number, currentColumn: number): number => {
19        // Ball successfully reached the bottom of the grid
20        if (currentRow === rowCount) {
21            return currentColumn;
22        }
23      
24        // Current cell redirects the ball to the right
25        if (grid[currentRow][currentColumn] === 1) {
26            // Check if ball hits right wall or forms a "V" shape with adjacent cell
27            if (currentColumn === columnCount - 1 || grid[currentRow][currentColumn + 1] === -1) {
28                return -1; // Ball gets stuck
29            }
30            // Continue tracing ball path: move down and right
31            return traceBallPath(currentRow + 1, currentColumn + 1);
32        } else {
33            // Current cell redirects the ball to the left
34            // Check if ball hits left wall or forms a "V" shape with adjacent cell
35            if (currentColumn === 0 || grid[currentRow][currentColumn - 1] === 1) {
36                return -1; // Ball gets stuck
37            }
38            // Continue tracing ball path: move down and left
39            return traceBallPath(currentRow + 1, currentColumn - 1);
40        }
41    };
42  
43    // Simulate dropping a ball from each column at the top
44    return Array.from({ length: columnCount }, (_, startColumn) => traceBallPath(0, startColumn));
45}
46

Time and Space Complexity

Time Complexity: O(m × n)

Each ball starting from the top row follows a unique path through the grid. In the worst case, a ball traverses from row 0 to row m-1, making exactly m recursive calls. Since we simulate the path for all n balls (one starting from each column), the total time complexity is O(m × n).

Space Complexity: O(m)

The space complexity is determined by the maximum depth of the recursive call stack. For each ball, the DFS function makes at most m recursive calls (one for each row traversed), resulting in a maximum recursion depth of m. Although we process n balls, we process them sequentially (not simultaneously), so only one recursive call stack exists at any given time. Therefore, the space complexity is O(m).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect V-Pattern Detection Logic

One of the most common mistakes is incorrectly identifying when a ball gets stuck in a V-shaped pattern. Developers often check the wrong adjacent cell or forget to verify both types of V-patterns.

Incorrect Implementation:

# Wrong: Only checking one type of V-pattern
if grid[i][j] == 1 and grid[i][j+1] == -1:
    return -1
# Missing the check for grid[i][j] == -1 and grid[i][j-1] == 1

Correct Implementation:

# Check both V-pattern scenarios
if grid[i][j] == 1 and j < n-1 and grid[i][j+1] == -1:
    return -1
if grid[i][j] == -1 and j > 0 and grid[i][j-1] == 1:
    return -1

2. Array Index Out of Bounds

Another frequent error is accessing array indices without proper boundary checks, especially when checking adjacent cells for V-patterns.

Incorrect Implementation:

# Wrong: No boundary check before accessing grid[i][j+1]
if grid[i][j] == 1 and grid[i][j+1] == -1:  # Can crash if j == n-1
    return -1

Correct Implementation:

# First check if we're at the boundary
if j == n-1 and grid[i][j] == 1:  # Wall collision check
    return -1
# Then safe to check V-pattern since we know j < n-1
if grid[i][j] == 1 and grid[i][j+1] == -1:
    return -1

3. Confusing Ball Movement Direction

A subtle but critical mistake is misunderstanding how the ball moves through the grid. The ball doesn't simply move down one row; it moves diagonally based on the board's direction.

Incorrect Implementation:

# Wrong: Ball only moves down, not diagonally
if grid[i][j] == 1:
    return dfs(i+1, j)  # Should be j+1
else:
    return dfs(i+1, j)  # Should be j-1

Correct Implementation:

# Ball moves diagonally based on board direction
if grid[i][j] == 1:
    return dfs(i+1, j+1)  # Move down-right
else:
    return dfs(i+1, j-1)  # Move down-left

4. Forgetting Base Case or Using Wrong Termination Condition

Some implementations forget to handle when the ball successfully exits the bottom, or use the wrong row index for termination.

Incorrect Implementation:

# Wrong: Using m-1 instead of m
if i == m-1:  # This means we're at the last row, not past it
    return j

Correct Implementation:

# Ball has passed through all rows (row index equals total rows)
if i == m:
    return j

Complete Corrected Solution Pattern:

def dfs(i, j):
    # Base case: successfully reached bottom
    if i == m:
        return j
  
    # Check boundaries first to avoid index errors
    if j == 0 and grid[i][j] == -1:
        return -1
    if j == n-1 and grid[i][j] == 1:
        return -1
  
    # Check V-patterns (boundaries already handled above)
    if grid[i][j] == 1 and grid[i][j+1] == -1:
        return -1
    if grid[i][j] == -1 and grid[i][j-1] == 1:
        return -1
  
    # Move diagonally based on board direction
    if grid[i][j] == 1:
        return dfs(i+1, j+1)
    else:
        return dfs(i+1, j-1)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More