1284. Minimum Number of Flips to Convert Binary Matrix to Zero Matrix
Problem Description
You are given a m x n
binary matrix mat
containing only 0s and 1s. Your goal is to transform this matrix into a zero matrix (all cells equal to 0) using the minimum number of steps.
In each step, you can:
- Choose any cell in the matrix
- Flip that cell and all its four neighbors (up, down, left, right) if they exist
- Flipping means changing
1
to0
and0
to1
- Neighbors are cells that share an edge with the chosen cell
- Flipping means changing
The problem asks you to return the minimum number of steps needed to convert the entire matrix to all zeros. If it's impossible to achieve this, return -1
.
For example, if you choose cell (i, j)
, the following cells get flipped:
- The cell itself:
(i, j)
- Top neighbor:
(i-1, j)
if it exists - Bottom neighbor:
(i+1, j)
if it exists - Left neighbor:
(i, j-1)
if it exists - Right neighbor:
(i, j+1)
if it exists
The solution uses BFS (Breadth-First Search) to explore all possible states of the matrix. Each state is represented as a binary number where each bit corresponds to a cell in the matrix (1 for cells with value 1, 0 for cells with value 0). The algorithm explores all reachable states level by level, tracking visited states to avoid cycles, until it either finds the zero matrix state or exhausts all possibilities.
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 matrix is a node, and edges connect states that can be reached by performing one flip operation. We're essentially exploring a state space graph.
Is it a tree?
- No: The state space is not a tree structure. Multiple paths can lead to the same matrix state (different sequences of flips can produce the same result).
Is the problem related to directed acyclic graphs?
- No: The state transitions are not necessarily acyclic. You can potentially return to a previous state by performing certain flip operations.
Is the problem related to shortest paths?
- Yes: We need to find the minimum number of steps (shortest path) from the initial matrix state to the zero matrix state. Each flip operation represents one edge in our state graph.
Is the graph weighted?
- No: Each flip operation counts as exactly one step, so all edges have the same weight (unweighted graph). We're looking for the minimum number of operations, not a weighted sum.
BFS
- Yes: Since we have an unweighted graph and need to find the shortest path, BFS is the appropriate algorithm.
Conclusion: The flowchart correctly leads us to use BFS (Breadth-First Search) for this problem. BFS is ideal here because:
- We're exploring a state space graph where each matrix configuration is a node
- We need the minimum number of steps (shortest path) to reach the target state (all zeros)
- The graph is unweighted (each flip operation has the same cost of 1)
- BFS guarantees finding the shortest path in an unweighted graph by exploring states level by level
Intuition
The key insight is recognizing that this is a state-space exploration problem. Each possible configuration of the matrix represents a unique state, and performing a flip operation transitions us from one state to another. Since we want the minimum number of operations, we need to find the shortest path from our initial state to the target state (all zeros).
Why can we model this as a graph problem? Consider that:
- Each matrix configuration is a node in our graph
- An edge exists between two nodes if one flip operation can transform one configuration into the other
- The problem asks for the minimum number of steps, which translates to finding the shortest path
Since the matrix is small (constraints typically limit it to a small size), the total number of possible states is manageable. For an m x n
matrix with binary values, there are at most 2^(m*n)
possible states.
To efficiently represent each state, we can encode the entire matrix as a single integer using bit manipulation. Each cell in the matrix corresponds to one bit position: if mat[i][j] = 1
, we set the bit at position i * n + j
. This gives us a unique integer for each matrix configuration.
The flip operation becomes a bit manipulation operation: when we choose to flip cell (i, j)
, we toggle the bits at positions corresponding to (i, j)
and its four neighbors. We can simulate all possible flip operations (choosing each cell as the flip center) and see which new states we can reach.
BFS is perfect here because:
- It explores states level by level, guaranteeing that when we first reach the target state (0), we've found it via the shortest path
- We can track visited states to avoid cycles and redundant work
- Each "level" in BFS represents all states reachable with exactly
k
flips
The algorithm terminates either when we reach state 0 (all cells are zero) and return the current level number, or when we've explored all reachable states without finding the target, in which case we return -1.
Learn more about Breadth-First Search patterns.
Solution Approach
The implementation uses BFS with bit manipulation to efficiently explore all possible matrix states:
1. State Encoding:
First, we encode the initial matrix into a single integer. Each cell mat[i][j]
that contains a 1
contributes to the state by setting the bit at position i * n + j
:
state = sum(1 << (i * n + j) for i in range(m) for j in range(n) if mat[i][j])
This creates a unique integer representation where each bit corresponds to a cell in the matrix.
2. BFS Initialization: We initialize a queue with the starting state and a visited set to track explored states:
q = deque([state]) vis = {state} ans = 0
3. Direction Array:
The dirs
array is cleverly designed to represent the current cell and its four neighbors:
dirs = [0, -1, 0, 1, 0, 0]
When iterating with index k
from 0 to 4:
k=0
:(0, 0)
- current cellk=1
:(-1, 0)
- top neighbork=2
:(0, 1)
- right neighbork=3
:(1, 0)
- bottom neighbork=4
:(0, -1)
- left neighbor (wraps around usingdirs[k+1]
)
4. Level-by-Level Exploration: The outer while loop processes states level by level, where each level represents all states reachable with the same number of flips:
while q:
for _ in range(len(q)): # Process all states at current level
state = q.popleft()
if state == 0: # Found target state
return ans
5. Generating Next States:
For each state, we try flipping every possible cell (i, j)
in the matrix:
for i in range(m):
for j in range(n):
nxt = state # Start with current state
6. Simulating Flip Operation: For each chosen cell, we flip it and its neighbors by toggling the corresponding bits:
for k in range(5):
x, y = i + dirs[k], j + dirs[k + 1]
if not 0 <= x < m or not 0 <= y < n:
continue # Skip out-of-bounds neighbors
if nxt & (1 << (x * n + y)): # If bit is 1
nxt -= 1 << (x * n + y) # Set to 0
else: # If bit is 0
nxt |= 1 << (x * n + y) # Set to 1
7. State Management: Only unvisited states are added to the queue for future exploration:
if nxt not in vis: vis.add(nxt) q.append(nxt)
8. Level Counter: After processing each level, we increment the answer counter:
ans += 1
The algorithm returns -1
if the queue becomes empty without finding the zero state, meaning it's impossible to convert the matrix to all zeros.
Time Complexity: O(2^(m*n) * m * n)
- We potentially visit all 2^(m*n)
states, and for each state, we try m * n
flip operations.
Space Complexity: O(2^(m*n))
- For storing visited states 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 how the BFS solution works.
Initial Matrix:
mat = [[1, 0], [0, 1]]
Step 1: Encode the Initial State
- Cell (0,0) = 1 β bit position 0*2 + 0 = 0
- Cell (0,1) = 0 β bit position 0*2 + 1 = 1
- Cell (1,0) = 0 β bit position 1*2 + 0 = 2
- Cell (1,1) = 1 β bit position 1*2 + 1 = 3
Initial state = 1001
in binary = 9 in decimal (bits 0 and 3 are set)
Step 2: BFS Level 0
- Queue: [9]
- Visited: {9}
- Check if state = 0? No, continue
Step 3: BFS Level 1 - Try All Possible Flips
Flip centered at (0,0):
- Flips: (0,0), (0,1), (1,0)
- Current bits: 1001
- Toggle bits at positions 0, 1, 2
- Result: 0110 = 6
- Add to queue since unvisited
Flip centered at (0,1):
- Flips: (0,0), (0,1), (1,1)
- Current bits: 1001
- Toggle bits at positions 0, 1, 3
- Result: 0011 = 3
- Add to queue since unvisited
Flip centered at (1,0):
- Flips: (0,0), (1,0), (1,1)
- Current bits: 1001
- Toggle bits at positions 0, 2, 3
- Result: 0110 = 6 (already in queue)
Flip centered at (1,1):
- Flips: (0,1), (1,0), (1,1)
- Current bits: 1001
- Toggle bits at positions 1, 2, 3
- Result: 1110 = 14
- Add to queue since unvisited
After Level 1:
- Queue: [6, 3, 14]
- Visited: {9, 6, 3, 14}
- ans = 1
Step 4: BFS Level 2 - Process State 6 (0110)
Flip centered at (0,0):
- Result: 1001 = 9 (already visited)
Flip centered at (0,1):
- Flips toggle bits 0, 1, 3
- Current: 0110
- Result: 1001 = 9 (already visited)
Flip centered at (1,0):
- Flips toggle bits 0, 2, 3
- Current: 0110
- Result: 1001 = 9 (already visited)
Flip centered at (1,1):
- Flips toggle bits 1, 2, 3
- Current: 0110
- Result: 0000 = 0 β
Found the target state 0!
The algorithm returns 2, meaning it takes a minimum of 2 steps to convert the matrix to all zeros.
Verification:
- Start: [[1,0],[0,1]]
- Flip at (1,1): [[1,1],[1,0]] (flipped cells: (0,1), (1,0), (1,1))
- Flip at (0,0): [[0,0],[0,0]] (flipped cells: (0,0), (0,1), (1,0))
The solution correctly finds that 2 flips are needed to transform the matrix to all zeros.
Solution Implementation
1class Solution:
2 def minFlips(self, mat: List[List[int]]) -> int:
3 # Get matrix dimensions
4 rows, cols = len(mat), len(mat[0])
5
6 # Convert initial matrix to a bit representation
7 # Each cell (i, j) is represented by bit at position (i * cols + j)
8 initial_state = sum(
9 1 << (i * cols + j)
10 for i in range(rows)
11 for j in range(cols)
12 if mat[i][j] == 1
13 )
14
15 # BFS queue starting with initial state
16 queue = deque([initial_state])
17 # Set to track visited states to avoid cycles
18 visited = {initial_state}
19 # Number of flip operations performed
20 flip_count = 0
21
22 # Direction vectors: current position, up, down, left, right
23 # Using a flat array to represent (0,0), (-1,0), (1,0), (0,-1), (0,1)
24 directions = [0, -1, 0, 1, 0, 0]
25
26 # BFS to find minimum flips to reach all-zeros state
27 while queue:
28 # Process all states at current level
29 level_size = len(queue)
30 for _ in range(level_size):
31 current_state = queue.popleft()
32
33 # Check if we've reached the target (all zeros)
34 if current_state == 0:
35 return flip_count
36
37 # Try flipping each cell in the matrix
38 for i in range(rows):
39 for j in range(cols):
40 next_state = current_state
41
42 # Flip current cell and its 4 adjacent cells
43 for k in range(5):
44 new_row = i + directions[k]
45 new_col = j + directions[k + 1]
46
47 # Check if the cell is within bounds
48 if not (0 <= new_row < rows and 0 <= new_col < cols):
49 continue
50
51 # Toggle the bit at position (new_row * cols + new_col)
52 bit_position = new_row * cols + new_col
53 if next_state & (1 << bit_position):
54 # If bit is 1, turn it to 0
55 next_state -= 1 << bit_position
56 else:
57 # If bit is 0, turn it to 1
58 next_state |= 1 << bit_position
59
60 # Add new state to queue if not visited
61 if next_state not in visited:
62 visited.add(next_state)
63 queue.append(next_state)
64
65 # Increment flip count after processing each level
66 flip_count += 1
67
68 # If no solution found, return -1
69 return -1
70
1class Solution {
2 public int minFlips(int[][] mat) {
3 int rows = mat.length;
4 int cols = mat[0].length;
5
6 // Convert the initial matrix to a bit state representation
7 // Each cell (i,j) is represented by bit at position (i * cols + j)
8 int initialState = 0;
9 for (int row = 0; row < rows; row++) {
10 for (int col = 0; col < cols; col++) {
11 if (mat[row][col] == 1) {
12 int bitPosition = row * cols + col;
13 initialState |= (1 << bitPosition);
14 }
15 }
16 }
17
18 // BFS queue to explore all possible states
19 Deque<Integer> queue = new ArrayDeque<>();
20 queue.offer(initialState);
21
22 // Set to track visited states to avoid cycles
23 Set<Integer> visitedStates = new HashSet<>();
24 visitedStates.add(initialState);
25
26 // Track the number of flips (BFS levels)
27 int flipsCount = 0;
28
29 // Direction array for accessing current cell and its 4 neighbors
30 // Format: up, current, down, left, right
31 int[] directions = {0, -1, 0, 1, 0, 0};
32
33 // BFS to find minimum flips to reach all-zeros state
34 while (!queue.isEmpty()) {
35 int levelSize = queue.size();
36
37 // Process all states at current BFS level
38 for (int i = 0; i < levelSize; i++) {
39 int currentState = queue.poll();
40
41 // Check if we've reached the target state (all zeros)
42 if (currentState == 0) {
43 return flipsCount;
44 }
45
46 // Try flipping each cell in the matrix
47 for (int row = 0; row < rows; row++) {
48 for (int col = 0; col < cols; col++) {
49 int nextState = currentState;
50
51 // Flip current cell and its 4 adjacent cells
52 for (int k = 0; k < 5; k++) {
53 int newRow = row + directions[k];
54 int newCol = col + directions[k + 1];
55
56 // Check boundaries
57 if (newRow < 0 || newRow >= rows || newCol < 0 || newCol >= cols) {
58 continue;
59 }
60
61 // Toggle the bit at position (newRow * cols + newCol)
62 int bitPosition = newRow * cols + newCol;
63 if ((nextState & (1 << bitPosition)) != 0) {
64 // If bit is 1, turn it to 0
65 nextState -= (1 << bitPosition);
66 } else {
67 // If bit is 0, turn it to 1
68 nextState |= (1 << bitPosition);
69 }
70 }
71
72 // Add new state to queue if not visited
73 if (!visitedStates.contains(nextState)) {
74 visitedStates.add(nextState);
75 queue.offer(nextState);
76 }
77 }
78 }
79 }
80
81 // Increment flip count after processing current level
82 flipsCount++;
83 }
84
85 // Return -1 if it's impossible to reach all-zeros state
86 return -1;
87 }
88}
89
1class Solution {
2public:
3 int minFlips(vector<vector<int>>& mat) {
4 int rows = mat.size();
5 int cols = mat[0].size();
6
7 // Convert the initial matrix to a bit state representation
8 // Each cell (i,j) is mapped to bit position (i * cols + j)
9 int initialState = 0;
10 for (int i = 0; i < rows; ++i) {
11 for (int j = 0; j < cols; ++j) {
12 if (mat[i][j]) {
13 initialState |= (1 << (i * cols + j));
14 }
15 }
16 }
17
18 // BFS queue to store states to explore
19 queue<int> bfsQueue{{initialState}};
20
21 // Set to track visited states to avoid cycles
22 unordered_set<int> visitedStates{{initialState}};
23
24 // Number of flips (BFS level)
25 int flipCount = 0;
26
27 // Direction vectors for self and 4 adjacent cells: (0,0), (-1,0), (0,1), (1,0), (0,-1)
28 vector<int> directions = {0, -1, 0, 1, 0, 0};
29
30 // BFS to find minimum flips to reach all-zero state
31 while (!bfsQueue.empty()) {
32 // Process all states at current BFS level
33 int levelSize = bfsQueue.size();
34 for (int t = 0; t < levelSize; ++t) {
35 int currentState = bfsQueue.front();
36 bfsQueue.pop();
37
38 // Check if we've reached the target state (all zeros)
39 if (currentState == 0) {
40 return flipCount;
41 }
42
43 // Try flipping each cell and its neighbors
44 for (int i = 0; i < rows; ++i) {
45 for (int j = 0; j < cols; ++j) {
46 int nextState = currentState;
47
48 // Flip current cell (i,j) and its 4 adjacent cells
49 for (int k = 0; k < 5; ++k) {
50 int newRow = i + directions[k];
51 int newCol = j + directions[k + 1];
52
53 // Skip if out of bounds
54 if (newRow < 0 || newRow >= rows || newCol < 0 || newCol >= cols) {
55 continue;
56 }
57
58 // Toggle the bit at position (newRow * cols + newCol)
59 int bitPosition = newRow * cols + newCol;
60 if ((nextState & (1 << bitPosition)) != 0) {
61 // If bit is 1, turn it to 0
62 nextState -= (1 << bitPosition);
63 } else {
64 // If bit is 0, turn it to 1
65 nextState |= (1 << bitPosition);
66 }
67 }
68
69 // Add new state to queue if not visited
70 if (!visitedStates.count(nextState)) {
71 visitedStates.insert(nextState);
72 bfsQueue.push(nextState);
73 }
74 }
75 }
76 }
77 ++flipCount;
78 }
79
80 // If no solution found, return -1
81 return -1;
82 }
83};
84
1function minFlips(mat: number[][]): number {
2 const rows = mat.length;
3 const cols = mat[0].length;
4
5 // Convert the initial matrix to a bit state representation
6 // Each cell (i,j) is mapped to bit position (i * cols + j)
7 let initialState = 0;
8 for (let i = 0; i < rows; i++) {
9 for (let j = 0; j < cols; j++) {
10 if (mat[i][j]) {
11 initialState |= (1 << (i * cols + j));
12 }
13 }
14 }
15
16 // BFS queue to store states to explore
17 const bfsQueue: number[] = [initialState];
18
19 // Set to track visited states to avoid cycles
20 const visitedStates = new Set<number>([initialState]);
21
22 // Number of flips (BFS level)
23 let flipCount = 0;
24
25 // Direction vectors for self and 4 adjacent cells: (0,0), (-1,0), (0,1), (1,0), (0,-1)
26 const directions = [0, -1, 0, 1, 0, 0];
27
28 // BFS to find minimum flips to reach all-zero state
29 while (bfsQueue.length > 0) {
30 // Process all states at current BFS level
31 const levelSize = bfsQueue.length;
32 for (let t = 0; t < levelSize; t++) {
33 const currentState = bfsQueue.shift()!;
34
35 // Check if we've reached the target state (all zeros)
36 if (currentState === 0) {
37 return flipCount;
38 }
39
40 // Try flipping each cell and its neighbors
41 for (let i = 0; i < rows; i++) {
42 for (let j = 0; j < cols; j++) {
43 let nextState = currentState;
44
45 // Flip current cell (i,j) and its 4 adjacent cells
46 for (let k = 0; k < 5; k++) {
47 const newRow = i + directions[k];
48 const newCol = j + directions[k + 1];
49
50 // Skip if out of bounds
51 if (newRow < 0 || newRow >= rows || newCol < 0 || newCol >= cols) {
52 continue;
53 }
54
55 // Toggle the bit at position (newRow * cols + newCol)
56 const bitPosition = newRow * cols + newCol;
57 if ((nextState & (1 << bitPosition)) !== 0) {
58 // If bit is 1, turn it to 0
59 nextState -= (1 << bitPosition);
60 } else {
61 // If bit is 0, turn it to 1
62 nextState |= (1 << bitPosition);
63 }
64 }
65
66 // Add new state to queue if not visited
67 if (!visitedStates.has(nextState)) {
68 visitedStates.add(nextState);
69 bfsQueue.push(nextState);
70 }
71 }
72 }
73 }
74 flipCount++;
75 }
76
77 // If no solution found, return -1
78 return -1;
79}
80
Time and Space Complexity
Time Complexity: O(2^(m*n) * m * n)
The algorithm uses BFS to explore all possible states of the matrix. Each cell in the m Γ n
matrix can be either 0 or 1, giving us 2^(m*n)
possible states. For each state, we iterate through all m * n
positions to generate next states by flipping cells. The flip operation for each position takes O(5)
= O(1)
time (flipping the cell itself and up to 4 adjacent cells). Since we visit each state at most once due to the visited set, the overall time complexity is O(2^(m*n) * m * n)
.
Space Complexity: O(2^(m*n))
The space is dominated by:
- The visited set
vis
which can store up to2^(m*n)
states in the worst case - The BFS queue
q
which can also contain up toO(2^(m*n))
states in the worst case - The state representation uses bit manipulation with an integer, requiring
O(1)
space per state
Therefore, the total space complexity is O(2^(m*n))
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Direction Array Indexing
One of the most common mistakes is misunderstanding how the direction array works. The array [0, -1, 0, 1, 0, 0]
might seem confusing at first glance.
Pitfall:
# Wrong interpretation - treating it as pairs
dirs = [0, -1, 0, 1, 0, 0]
for k in range(5):
x = i + dirs[2*k] # This would cause index out of bounds
y = j + dirs[2*k + 1]
Correct Approach:
dirs = [0, -1, 0, 1, 0, 0]
for k in range(5):
x = i + dirs[k] # Uses k and k+1 as sliding window
y = j + dirs[k + 1]
The trick is that dirs[k]
and dirs[k+1]
form sliding pairs that represent:
- (0,0), (-1,0), (0,1), (1,0), (0,-1)
2. Bit Manipulation Errors When Toggling
Another frequent mistake occurs when toggling bits, especially confusing XOR with conditional logic.
Pitfall:
# Using XOR without checking might seem simpler but can be error-prone next_state ^= (1 << bit_position) # This works but less readable
Better Approach:
# Explicitly check and toggle for clarity if next_state & (1 << bit_position): # If bit is 1 next_state -= 1 << bit_position # Turn to 0 else: # If bit is 0 next_state |= 1 << bit_position # Turn to 1
3. State Encoding Formula Confusion
Getting the bit position calculation wrong is easy when converting between 2D and 1D representations.
Pitfall:
# Wrong formula - using cols for row offset bit_position = i * rows + j # Incorrect!
Correct Formula:
bit_position = i * cols + j # Row index * number of columns + column index
4. Not Processing BFS Level by Level
Forgetting to process all nodes at the current level before incrementing the counter leads to incorrect step counts.
Pitfall:
while queue: state = queue.popleft() if state == 0: return flip_count flip_count += 1 # Wrong! Increments for each state, not each level # ... generate next states
Correct Implementation:
while queue:
level_size = len(queue)
for _ in range(level_size): # Process entire level
state = queue.popleft()
if state == 0:
return flip_count
# ... generate next states
flip_count += 1 # Increment after processing whole level
5. Matrix Size Limitations
Not considering the practical limitations of bit manipulation for larger matrices.
Issue:
- Python integers can handle arbitrary precision, but the algorithm's time/space complexity makes it impractical for matrices larger than ~20 cells total
- For a 5Γ4 matrix (20 cells), we could have up to 2^20 = 1,048,576 states
Solution: Add a size check at the beginning:
def minFlips(self, mat: List[List[int]]) -> int:
rows, cols = len(mat), len(mat[0])
# Practical limitation check
if rows * cols > 20:
# Consider alternative approaches for larger matrices
raise ValueError("Matrix too large for exhaustive BFS approach")
# ... rest of the implementation
6. Off-by-One Errors in Boundary Checking
Using wrong comparison operators for boundary checks.
Pitfall:
# Wrong boundary check if 0 < new_row < rows and 0 < new_col < cols: # Excludes row/col 0! continue
Correct:
if not (0 <= new_row < rows and 0 <= new_col < cols): continue
In a binary min heap, the minimum element can be found in:
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
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
Want a Structured Path to Master System Design Too? Donβt Miss This!