1706. Where Will the Ball Fall
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:
- 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) - 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
.
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) andgrid[i][j+1] = -1
(directing left), the ball gets trapped between them - Similarly, if
grid[i][j] = -1
(directing left) andgrid[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:
-
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
-
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
-
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"
-
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 callingdfs(i+1, j+1)
- If
grid[i][j] == -1
: Move diagonally down-left by callingdfs(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 EvaluatorExample 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)
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!