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:
- We're exploring a state space graph where each grid configuration is a state
- We need the minimum number of moves (shortest path)
- All moves have equal cost (unweighted graph)
- BFS guarantees finding the shortest path in an unweighted graph
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:
- Equal cost moves: Every stone move counts as exactly 1 operation, making this an unweighted graph problem
- 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
- 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 withpairwise
to generate the 4 adjacent directions: up, down, left, rightpairwise(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)
returnsTrue
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
- Try moving one stone to each adjacent cell
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 EvaluatorExample 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
- Right to (0,2): Has 0 stones → Creates
Continuing this process, one possible sequence:
- Move from (0,0) to (0,1):
[[1,2,0],[1,1,0],[1,1,2]]
- Move from (0,1) to (0,2):
[[1,1,1],[1,1,0],[1,1,2]]
- 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]
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Want a Structured Path to Master System Design Too? Don’t Miss This!