Facebook Pixel

1284. Minimum Number of Flips to Convert Binary Matrix to Zero Matrix

HardBit ManipulationBreadth-First SearchArrayHash TableMatrix
Leetcode Link

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:

  1. Choose any cell in the matrix
  2. Flip that cell and all its four neighbors (up, down, left, right) if they exist
    • Flipping means changing 1 to 0 and 0 to 1
    • Neighbors are cells that share an edge with the chosen cell

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:

  1. We're exploring a state space graph where each matrix configuration is a node
  2. We need the minimum number of steps (shortest path) to reach the target state (all zeros)
  3. The graph is unweighted (each flip operation has the same cost of 1)
  4. BFS guarantees finding the shortest path in an unweighted graph by exploring states level by level
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 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:

  1. It explores states level by level, guaranteeing that when we first reach the target state (0), we've found it via the shortest path
  2. We can track visited states to avoid cycles and redundant work
  3. 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 cell
  • k=1: (-1, 0) - top neighbor
  • k=2: (0, 1) - right neighbor
  • k=3: (1, 0) - bottom neighbor
  • k=4: (0, -1) - left neighbor (wraps around using dirs[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 Evaluator

Example 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:

  1. Start: [[1,0],[0,1]]
  2. Flip at (1,1): [[1,1],[1,0]] (flipped cells: (0,1), (1,0), (1,1))
  3. 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 to 2^(m*n) states in the worst case
  • The BFS queue q which can also contain up to O(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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the minimum element can be found in:


Recommended Readings

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

Load More