Facebook Pixel

2850. Minimum Moves to Spread Stones Over Grid

Problem Description

You have a 3x3 grid containing exactly 9 stones total. Each cell in the grid contains a certain number of stones (could be 0, 1, or multiple stones in a single cell).

Your goal is to redistribute these stones so that each cell contains exactly 1 stone. You can move stones between cells with the following rule:

  • In one move, you can take a single stone from any cell and move it to an adjacent cell (cells that share a side - up, down, left, or right, but not diagonal).

The problem asks you to find the minimum number of moves needed to achieve a state where every cell has exactly 1 stone.

For example, if you have a grid like:

[[2, 1, 0],
 [1, 1, 0],
 [1, 1, 2]]

You would need to move stones from cells with 2 stones to the cells with 0 stones. The task is to find the optimal sequence of moves that results in:

[[1, 1, 1],
 [1, 1, 1],
 [1, 1, 1]]

The solution uses BFS to explore all possible states of the grid. Each state represents a different distribution of the 9 stones across the grid. From any given state, we can generate new states by moving a stone from a cell with more than 1 stone to an adjacent cell with less than 2 stones. The BFS continues until we reach the target state where all cells have exactly 1 stone, and returns the number of steps taken to reach this state.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: This problem can be modeled as a graph where each state of the grid (a specific distribution of stones) is a node, and edges connect states that can be reached by moving one stone to an adjacent cell.

Is it a tree?

  • No: The state graph is not a tree because multiple paths can lead to the same state (different sequences of moves can result in the same stone distribution).

Is the problem related to directed acyclic graphs?

  • No: The state graph can have cycles (you could potentially move stones back and forth between cells).

Is the problem related to shortest paths?

  • Yes: We need to find the minimum number of moves (shortest path) from the initial state to the target state where all cells have exactly 1 stone.

Is the graph Weighted?

  • No: Each move (edge) has the same cost of 1, making this an unweighted graph problem.

BFS

  • Yes: For unweighted shortest path problems, BFS is the optimal choice.

Conclusion: The flowchart correctly leads us to use BFS (Breadth-First Search) for this problem. BFS is perfect here because:

  1. We're exploring a state space graph where each grid configuration is a state
  2. We need the minimum number of moves (shortest path)
  3. All moves have equal cost (unweighted graph)
  4. BFS guarantees finding the shortest path in an unweighted graph
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that this is fundamentally a state transformation problem. We start with an initial configuration of stones and need to reach a specific target configuration where each cell has exactly 1 stone.

Think of each possible arrangement of the 9 stones on the 3x3 grid as a unique "state". Moving a stone from one cell to an adjacent cell transforms one state into another. This naturally forms a graph where:

  • Each node represents a possible grid configuration
  • Each edge represents a single stone move between adjacent cells

Since we want the minimum number of moves, we're essentially looking for the shortest path from our initial state to the target state [[1,1,1],[1,1,1],[1,1,1]].

Why BFS works perfectly here:

  1. Equal cost moves: Every stone move counts as exactly 1 operation, making this an unweighted graph problem
  2. Level-by-level exploration: BFS explores states in order of their distance from the start. The first time we encounter the target state, we've found it via the shortest path
  3. State deduplication: We track visited states to avoid exploring the same configuration multiple times through different move sequences

The algorithm generates new states by:

  • Finding cells with more than 1 stone (these have stones to give)
  • Moving one stone to an adjacent cell that has less than 2 stones (room to receive)
  • Each such move creates a new state one step further from the initial state

When BFS reaches the target state where all cells have exactly 1 stone, the current level number gives us the minimum moves required. This approach guarantees optimality because BFS always finds the shortest path in an unweighted graph.

Learn more about Breadth-First Search and Dynamic Programming patterns.

Solution Approach

The implementation uses BFS with state representation and deduplication to find the minimum moves:

1. State Representation

  • Each grid configuration is converted to a tuple of tuples for immutability and hashability: tuple(tuple(row) for row in grid)
  • This allows us to store states in a set for efficient duplicate checking

2. BFS Setup

  • Initialize a queue q with the initial grid state
  • Create a visited set vis to track explored states
  • Set ans = 0 to count the number of levels (moves)

3. Direction Array

  • dirs = (-1, 0, 1, 0, -1) is used with pairwise to generate the 4 adjacent directions: up, down, left, right
  • pairwise(dirs) yields: (-1, 0), (0, 1), (1, 0), (0, -1)

4. BFS Main Loop

  • Process all states at the current level before incrementing ans
  • For each state, check if it's the target: all(x for row in cur for x in row) returns True only when all cells have at least 1 stone (no zeros)

5. State Transition Generation

  • For each cell (i, j) with more than 1 stone (cur[i][j] > 1):
    • Try moving one stone to each adjacent cell (x, y)
    • Only move if the adjacent cell is within bounds and has less than 2 stones (cur[x][y] < 2)
    • Create a new state by:
      • Copying the current grid
      • Decreasing the source cell by 1: nxt[i][j] -= 1
      • Increasing the target cell by 1: nxt[x][y] += 1

6. State Deduplication

  • Convert the new state to tuple form
  • Only add to queue if not previously visited
  • Mark as visited to prevent redundant exploration

7. Level Tracking

  • After processing all states at the current level, increment ans
  • This ensures ans represents the number of moves when the target is found

The algorithm continues until the target state is reached, at which point it returns the current level count as the minimum number of moves required.

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 BFS solution approach.

Initial Grid:

[[2, 1, 0],
 [1, 1, 0],
 [1, 1, 2]]

Target: Every cell should have exactly 1 stone.

Step 0 (Initial State):

  • Queue: [[[2,1,0],[1,1,0],[1,1,2]]]
  • Visited: {((2,1,0),(1,1,0),(1,1,2))}
  • Check if target reached: No (we have cells with 0 stones)

Step 1 (Level 1 - Finding all states reachable in 1 move):

From the initial state, we identify cells with more than 1 stone:

  • Cell (0,0) has 2 stones
  • Cell (2,2) has 2 stones

From cell (0,0) with 2 stones, we can move to:

  • Right to (0,1): Already has 1 stone, can't receive more
  • Down to (1,0): Already has 1 stone, can't receive more

From cell (2,2) with 2 stones, we can move to:

  • Up to (1,2): Has 0 stones, can receive → Creates state [[2,1,0],[1,1,1],[1,1,1]]
  • Left to (2,1): Already has 1 stone, can't receive more

New states after 1 move:

  • [[2,1,0],[1,1,1],[1,1,1]] (moved from (2,2) to (1,2))

Step 2 (Level 2 - States reachable in 2 moves):

From state [[2,1,0],[1,1,1],[1,1,1]]:

  • Cell (0,0) has 2 stones, can move to:
    • Right to (0,1): Already has 1 stone, can't receive
    • Down to (1,0): Already has 1 stone, can't receive

Wait, we need to find a different path. Let me reconsider...

Actually, from cell (0,0) with 2 stones in the initial state:

  • We can move to cell (0,1) which has 1 stone (making it 2)
  • This creates state [[1,2,0],[1,1,0],[1,1,2]]

From this new state [[1,2,0],[1,1,0],[1,1,2]]:

  • Cell (0,1) has 2 stones, can move to:
    • Right to (0,2): Has 0 stones → Creates [[1,1,1],[1,1,0],[1,1,2]]
    • Down to (1,1): Already has 1 stone
    • Left to (0,0): Already has 1 stone

Continuing this process, one possible sequence:

  1. Move from (0,0) to (0,1): [[1,2,0],[1,1,0],[1,1,2]]
  2. Move from (0,1) to (0,2): [[1,1,1],[1,1,0],[1,1,2]]
  3. Move from (2,2) to (1,2): [[1,1,1],[1,1,1],[1,1,1]] ✓ Target reached!

Result: Minimum 3 moves needed.

The BFS explores all possible move sequences level by level, ensuring that when we first encounter the target state [[1,1,1],[1,1,1],[1,1,1]], we've found it through the shortest path possible. The visited set prevents us from exploring the same configuration twice, even if we can reach it through different move sequences.

Solution Implementation

1from collections import deque
2from itertools import pairwise
3from typing import List
4
5class Solution:
6    def minimumMoves(self, grid: List[List[int]]) -> int:
7        # Initialize BFS queue with the initial grid state (converted to immutable tuple)
8        queue = deque([tuple(tuple(row) for row in grid)])
9      
10        # Track visited states to avoid revisiting
11        visited = set(queue)
12      
13        # Initialize move counter
14        moves = 0
15      
16        # Direction vectors for adjacent cells (up, right, down, left)
17        directions = (-1, 0, 1, 0, -1)
18      
19        # BFS to find minimum moves
20        while True:
21            # Process all states at current level
22            for _ in range(len(queue)):
23                current_state = queue.popleft()
24              
25                # Check if target state reached (all cells have exactly 1 stone)
26                if all(cell == 1 for row in current_state for cell in row):
27                    return moves
28              
29                # Try moving stones from cells with more than 1 stone
30                for row_idx in range(3):
31                    for col_idx in range(3):
32                        # Only move from cells with excess stones
33                        if current_state[row_idx][col_idx] > 1:
34                            # Try moving to all 4 adjacent cells
35                            for dx, dy in pairwise(directions):
36                                new_row = row_idx + dx
37                                new_col = col_idx + dy
38                              
39                                # Check if target cell is valid and has less than 2 stones
40                                if (0 <= new_row < 3 and 
41                                    0 <= new_col < 3 and 
42                                    current_state[new_row][new_col] < 2):
43                                  
44                                    # Create new state after moving one stone
45                                    next_state = [list(row) for row in current_state]
46                                    next_state[row_idx][col_idx] -= 1
47                                    next_state[new_row][new_col] += 1
48                                  
49                                    # Convert to immutable tuple for hashing
50                                    next_state_tuple = tuple(tuple(row) for row in next_state)
51                                  
52                                    # Add to queue if not visited
53                                    if next_state_tuple not in visited:
54                                        visited.add(next_state_tuple)
55                                        queue.append(next_state_tuple)
56          
57            # Increment move counter after processing current level
58            moves += 1
59
1class Solution {
2    /**
3     * Finds the minimum number of moves to make each cell in a 3x3 grid contain exactly 1 stone.
4     * Uses BFS to explore all possible states until reaching the target state.
5     * 
6     * @param grid The initial 3x3 grid configuration
7     * @return The minimum number of moves required
8     */
9    public int minimumMoves(int[][] grid) {
10        // Initialize BFS queue with the initial grid state
11        Deque<String> queue = new ArrayDeque<>();
12        queue.add(gridToString(grid));
13      
14        // Track visited states to avoid revisiting
15        Set<String> visited = new HashSet<>();
16        visited.add(gridToString(grid));
17      
18        // Direction vectors for moving up, right, down, left
19        int[] directions = {-1, 0, 1, 0, -1};
20      
21        // BFS level-by-level traversal
22        for (int moves = 0;; ++moves) {
23            // Process all states at the current level
24            for (int levelSize = queue.size(); levelSize > 0; --levelSize) {
25                String currentState = queue.poll();
26              
27                // Check if we've reached the target state (all cells have exactly 1 stone)
28                if ("111111111".equals(currentState)) {
29                    return moves;
30                }
31              
32                // Convert string back to grid for processing
33                int[][] currentGrid = stringToGrid(currentState);
34              
35                // Try moving stones from cells with more than 1 stone
36                for (int row = 0; row < 3; ++row) {
37                    for (int col = 0; col < 3; ++col) {
38                        // Only move stones from cells with more than 1 stone
39                        if (currentGrid[row][col] > 1) {
40                            // Try all four directions
41                            for (int dir = 0; dir < 4; ++dir) {
42                                int newRow = row + directions[dir];
43                                int newCol = col + directions[dir + 1];
44                              
45                                // Check if the new position is valid and needs stones
46                                if (newRow >= 0 && newRow < 3 && newCol >= 0 && newCol < 3 
47                                    && currentGrid[newRow][newCol] < 2) {
48                                  
49                                    // Create a new grid state after the move
50                                    int[][] nextGrid = new int[3][3];
51                                    for (int i = 0; i < 3; ++i) {
52                                        for (int j = 0; j < 3; ++j) {
53                                            nextGrid[i][j] = currentGrid[i][j];
54                                        }
55                                    }
56                                  
57                                    // Move one stone from current cell to adjacent cell
58                                    nextGrid[row][col]--;
59                                    nextGrid[newRow][newCol]++;
60                                  
61                                    // Convert to string and add to queue if not visited
62                                    String nextState = gridToString(nextGrid);
63                                    if (!visited.contains(nextState)) {
64                                        visited.add(nextState);
65                                        queue.add(nextState);
66                                    }
67                                }
68                            }
69                        }
70                    }
71                }
72            }
73        }
74    }
75
76    /**
77     * Converts a 3x3 grid to a string representation for efficient state comparison.
78     * 
79     * @param grid The 3x3 grid to convert
80     * @return String representation of the grid
81     */
82    private String gridToString(int[][] grid) {
83        StringBuilder stringBuilder = new StringBuilder();
84        for (int[] row : grid) {
85            for (int value : row) {
86                stringBuilder.append(value);
87            }
88        }
89        return stringBuilder.toString();
90    }
91
92    /**
93     * Converts a string representation back to a 3x3 grid.
94     * 
95     * @param s The string representation of the grid
96     * @return The reconstructed 3x3 grid
97     */
98    private int[][] stringToGrid(String s) {
99        int[][] grid = new int[3][3];
100        for (int row = 0; row < 3; ++row) {
101            for (int col = 0; col < 3; ++col) {
102                grid[row][col] = s.charAt(row * 3 + col) - '0';
103            }
104        }
105        return grid;
106    }
107}
108
1class Solution {
2public:
3    int minimumMoves(vector<vector<int>>& grid) {
4        // BFS queue to store grid states as strings
5        queue<string> bfsQueue;
6        // Convert initial grid to string representation and add to queue
7        bfsQueue.push(gridToString(grid));
8      
9        // Set to track visited states to avoid revisiting
10        unordered_set<string> visited;
11        visited.insert(gridToString(grid));
12      
13        // Direction vectors for moving up, right, down, left
14        vector<int> directions = {-1, 0, 1, 0, -1};
15
16        // BFS level-by-level traversal
17        for (int moves = 0;; ++moves) {
18            int currentLevelSize = bfsQueue.size();
19          
20            // Process all states at current level
21            while (currentLevelSize--) {
22                string currentState = bfsQueue.front();
23                bfsQueue.pop();
24              
25                // Check if we've reached the target state (all cells have value 1)
26                if (currentState == "111111111") {
27                    return moves;
28                }
29              
30                // Convert string back to grid for manipulation
31                vector<vector<int>> currentGrid = stringToGrid(currentState);
32
33                // Try moving stones from cells with more than 1 stone
34                for (int row = 0; row < 3; ++row) {
35                    for (int col = 0; col < 3; ++col) {
36                        // Only move from cells with excess stones (value > 1)
37                        if (currentGrid[row][col] > 1) {
38                            // Try all four directions
39                            for (int dir = 0; dir < 4; ++dir) {
40                                int newRow = row + directions[dir];
41                                int newCol = col + directions[dir + 1];
42                              
43                                // Check if new position is valid and has less than 2 stones
44                                if (newRow >= 0 && newRow < 3 && 
45                                    newCol >= 0 && newCol < 3 && 
46                                    currentGrid[newRow][newCol] < 2) {
47                                  
48                                    // Create new grid state after moving one stone
49                                    vector<vector<int>> nextGrid = currentGrid;
50                                    nextGrid[row][col]--;      // Remove stone from source
51                                    nextGrid[newRow][newCol]++; // Add stone to destination
52                                  
53                                    // Convert to string and check if already visited
54                                    string nextState = gridToString(nextGrid);
55                                    if (!visited.count(nextState)) {
56                                        visited.insert(nextState);
57                                        bfsQueue.push(nextState);
58                                    }
59                                }
60                            }
61                        }
62                    }
63                }
64            }
65        }
66    }
67
68private:
69    // Convert a 3x3 grid to string representation for easy comparison and hashing
70    string gridToString(const vector<vector<int>>& grid) {
71        string result;
72        for (const auto& row : grid) {
73            for (int value : row) {
74                result += to_string(value);
75            }
76        }
77        return result;
78    }
79
80    // Convert string representation back to 3x3 grid
81    vector<vector<int>> stringToGrid(const string& state) {
82        vector<vector<int>> grid(3, vector<int>(3));
83        for (int row = 0; row < 3; ++row) {
84            for (int col = 0; col < 3; ++col) {
85                // Convert character to integer by subtracting '0'
86                grid[row][col] = state[row * 3 + col] - '0';
87            }
88        }
89        return grid;
90    }
91};
92
1/**
2 * Finds the minimum number of moves to make each cell in a 3x3 grid contain exactly 1 stone.
3 * Uses BFS to explore all possible states until reaching the target state.
4 * @param grid - 3x3 grid where each cell contains the number of stones
5 * @returns Minimum number of moves required
6 */
7function minimumMoves(grid: number[][]): number {
8    // Queue for BFS traversal
9    const queue: string[] = [f(grid)];
10    // Set to track visited states to avoid cycles
11    const visited: Set<string> = new Set([f(grid)]);
12    // Direction vectors for moving up, right, down, left
13    const directions: number[] = [-1, 0, 1, 0, -1];
14
15    // BFS level by level
16    for (let moveCount = 0; ; moveCount++) {
17        let levelSize = queue.length;
18      
19        // Process all states at current level
20        while (levelSize-- > 0) {
21            const currentState = queue.shift()!;
22          
23            // Check if we've reached the target state (all cells have exactly 1 stone)
24            if (currentState === '111111111') {
25                return moveCount;
26            }
27          
28            // Convert string state back to 2D grid
29            const currentGrid = g(currentState);
30
31            // Try moving stones from cells with more than 1 stone
32            for (let row = 0; row < 3; row++) {
33                for (let col = 0; col < 3; col++) {
34                    // Only move from cells with excess stones
35                    if (currentGrid[row][col] > 1) {
36                        // Try all four directions
37                        for (let dir = 0; dir < 4; dir++) {
38                            const newRow = row + directions[dir];
39                            const newCol = col + directions[dir + 1];
40                          
41                            // Check if target position is valid and has less than 2 stones
42                            if (newRow >= 0 && newRow < 3 && newCol >= 0 && newCol < 3 && 
43                                currentGrid[newRow][newCol] < 2) {
44                                // Create new grid state after moving one stone
45                                const nextGrid = currentGrid.map(gridRow => [...gridRow]);
46                                nextGrid[row][col]--;
47                                nextGrid[newRow][newCol]++;
48                              
49                                // Convert to string and add to queue if not visited
50                                const nextState = f(nextGrid);
51                                if (!visited.has(nextState)) {
52                                    visited.add(nextState);
53                                    queue.push(nextState);
54                                }
55                            }
56                        }
57                    }
58                }
59            }
60        }
61    }
62}
63
64/**
65 * Converts a 3x3 grid to a string representation for state tracking.
66 * @param grid - 3x3 grid of numbers
67 * @returns String representation of the grid (flattened and concatenated)
68 */
69function f(grid: number[][]): string {
70    return grid.flat().join('');
71}
72
73/**
74 * Converts a string representation back to a 3x3 grid.
75 * @param s - String representation of the grid (9 characters)
76 * @returns 3x3 grid of numbers
77 */
78function g(s: string): number[][] {
79    return Array.from({ length: 3 }, (_, rowIndex) =>
80        Array.from({ length: 3 }, (_, colIndex) => Number(s[rowIndex * 3 + colIndex]))
81    );
82}
83

Time and Space Complexity

Time Complexity: O((3^9)! × 9 × 4) which simplifies to O((3^9)!)

The algorithm uses BFS to explore different grid states. Each state is a 3×3 grid configuration. The maximum number of unique states is bounded by the number of ways to distribute stones across 9 cells. Since the total number of stones is fixed at 9 (one per cell in the final state), and each cell can have 0 to 9 stones theoretically, the upper bound on states is approximately 3^9 (though the actual number is much smaller due to constraints). For each state, we examine all 9 cells, and for cells with more than 1 stone, we check 4 adjacent directions. The BFS ensures each state is visited at most once. The factorial-like growth comes from the massive state space of possible grid configurations.

Space Complexity: O((3^9)!)

The space is dominated by the vis set which stores all visited grid states to avoid revisiting them. In the worst case, we might need to store nearly all possible valid grid configurations before finding the solution. The queue q also stores states but its size is bounded by the number of states at any BFS level, which is less than the total number of visited states. Each state itself requires O(9) space to store the 3×3 grid, but this is constant compared to the number of states stored.

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

Common Pitfalls

1. Incorrect Target State Check

A critical pitfall is checking if the grid has reached the target state incorrectly. The current code uses:

if all(cell == 1 for row in current_state for cell in row):
    return moves

However, this only verifies that all cells have exactly 1 stone. In cases where the initial grid doesn't have exactly 9 stones total, this check would never succeed, causing an infinite loop.

Solution: Add validation to ensure the grid has exactly 9 stones before starting BFS:

def minimumMoves(self, grid: List[List[int]]) -> int:
    # Validate total stone count
    total_stones = sum(cell for row in grid for cell in row)
    if total_stones != 9:
        return -1  # Invalid input
  
    # Continue with BFS...

2. Moving Stones to Already-Full Cells

The condition current_state[new_row][new_col] < 2 prevents moving stones to cells that already have 2 or more stones. However, this is overly restrictive - we should only prevent moves to cells that already have exactly 1 stone in the target configuration.

Solution: Change the condition to allow moves to any cell with 0 stones:

if (0 <= new_row < 3 and 
    0 <= new_col < 3 and 
    current_state[new_row][new_col] == 0):  # Only move to empty cells

3. Inefficient State Representation Conversion

Converting between list and tuple representations repeatedly is computationally expensive:

next_state = [list(row) for row in current_state]  # Tuple to list
# ... modifications ...
next_state_tuple = tuple(tuple(row) for row in next_state)  # List to tuple

Solution: Create a helper function to handle state modifications more efficiently:

def create_next_state(current, src_row, src_col, dst_row, dst_col):
    next_state = tuple(
        tuple(
            cell - 1 if (i, j) == (src_row, src_col) else
            cell + 1 if (i, j) == (dst_row, dst_col) else
            cell
            for j, cell in enumerate(row)
        )
        for i, row in enumerate(current)
    )
    return next_state

4. Missing Early Termination Check

The code doesn't check if the initial state is already the target state, leading to unnecessary BFS iterations.

Solution: Check the initial state before starting BFS:

def minimumMoves(self, grid: List[List[int]]) -> int:
    # Check if already in target state
    if all(cell == 1 for row in grid for cell in row):
        return 0
  
    # Initialize BFS...

5. Memory Overhead from Storing Full Grid States

Storing complete 3x3 grids in the visited set can consume significant memory for larger problems or variations.

Solution: Consider using a more compact state representation, such as encoding the grid as a single integer or string:

def encode_state(grid):
    # Encode 3x3 grid as a single integer (each cell uses 4 bits)
    encoded = 0
    for row in grid:
        for cell in row:
            encoded = (encoded << 4) | cell
    return encoded

def decode_state(encoded):
    grid = []
    for _ in range(3):
        row = []
        for _ in range(3):
            row.append(encoded & 0xF)
            encoded >>= 4
        grid.append(row[::-1])
    return grid[::-1]
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

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

Load More