Given a m x n
binary matrix mat
. In one step, you can choose one cell and flip it and all the four neighbours of it if they exist (Flip is changing 1 to 0 and 0 to 1). A pair of cells are called neighboors if they share one edge.
Return the minimum number of steps required to convert mat
to a zero matrix or -1 if you cannot.
Binary matrix is a matrix with all cells equal to 0 or 1 only.
Zero matrix is a matrix with all cells equal to 0.
Example 1:
Input: mat = [[0,0],[0,1]]
Output: 3
Explanation: One possible solution is to flip (1, 0) then (0, 1) and finally (1, 1) as shown.
Example 2:
Input: mat = [[0]]
Output: 0
Explanation: Given matrix is a zero matrix. We don't need to change it.
Example 3:
Input: mat = [[1,1,1],[1,0,1],[0,0,0]]
Output: 6
Example 4:
Input: mat = [[1,0,0],[1,0,0]]
Output: -1
Explanation: Given matrix can't be a zero matrix
Constraints:
m == mat.length
n == mat[0].length
1 <= m <= 3
1 <= n <= 3
mat[i][j]
is 0 or 1.
Solution 1. DFS + Backtracking + Memoization
For each cell, either we do not flip it at all or we flip it one time. There is no point flipping any cell more than one time. This means we have 2^(m * n) possible states. m and n are at most 3, so we can check each possible state reachable from the initial state and get the min cost to transit from the initial state to the final 0 state.
Info bookkeeping
1. To avoid computing the same state more than once, we create a dp array. dp[i] is the minimum flips needed to transit from state i to state 0.
2. If a flip causes state go from i1 to i2 and i2 has been computed before, this means we just entered a cycle, wasted some flips and made no state transition progress. So we need to have a boolean array seen to track if we have seen a state before. If we have, return -1 to indicate the flips that led to this cycle is not going to give us a possible answer.
Algorithm
1. initialize dp and seen array, dp[0] = 0;
2. starting from the initial state, try out each different cell to flip, recursively solve all different next states, then get the minimum cost among all possible next states. The recursion method is defined as follows.
a. if a state has been seen before, return -1 to indicate the previous flip picks is not going to yield an answer;
b. else if a state has been computed and does have a valid answer to it, return it without re-computing it;
c. otherwise, we need to compute this state using backtracking. Mark this state as seen, then for each possible next state, do a flip that leads to the next state, recursively solve for this next state, then flip the same cell back to finish the backtracking. After solving all possible next states and updating the current state's result, mark the current state as unseen to finish backtracking.
The runtime is O(2^(m * n)) since we only check each state once.
class Solution { private int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1}; private int[] dp; //dp[i]: the minimum flips needed transitioning from state i to state 0 private boolean[] seen; public int minFlips(int[][] mat) { int m = mat.length, n = mat[0].length, ans = Integer.MAX_VALUE; dp = new int[1 << (m * n)]; seen = new boolean[1 << (m * n)]; Arrays.fill(dp, Integer.MAX_VALUE); dp[0] = 0; return dfs(mat); } private int dfs(int[][] mat) { int state = getState(mat); if(seen[state]) { return -1; } if(dp[state] < Integer.MAX_VALUE) { return dp[state]; } seen[state] = true; int minCost = Integer.MAX_VALUE; for(int i = 0; i < mat.length; i++) { for(int j = 0; j < mat[0].length; j++) { flipAt(mat, i, j); int currMin = dfs(mat); if(currMin >= 0) { minCost = Math.min(minCost, currMin); } flipAt(mat, i, j); } } dp[state] = (minCost == Integer.MAX_VALUE ? -1 : minCost + 1); seen[state] = false; return dp[state]; } private void flipAt(int[][] mat, int x, int y) { mat[x][y] = (mat[x][y] == 0 ? 1 : 0); for(int dir = 0; dir < 4; dir++) { int x1 = x + dx[dir]; int y1 = y + dy[dir]; if(x1 >= 0 && x1 < mat.length && y1 >= 0 && y1 < mat[0].length) { mat[x1][y1] = (mat[x1][y1] == 0 ? 1 : 0); } } } private int getState(int[][] mat) { int state = 0, cnt = 0; for(int i = 0; i < mat.length; i++) { for(int j = 0; j < mat[0].length; j++) { state = (state | (mat[i][j] << cnt)); cnt++; } } return state; } }
Solution 2. BFS + Memoization + Awesome bit operations
Another way of solving this problem is to use bfs. Since the problem asks for the minimum number of flips to get to state 0, we can treat a flip from state S1 to state S2 as traveling from node S1 to S2 in one step. Just like in solution 1, we still track each state's result. BFS ensures that the 1st time we update one state's result, it is guaranteed to be the minimum cost going from the initial state. We only add new states to the bfs queue in this case. If we later get a bigger cost for the same state, we just ignore it. This ensures that we only compute each possible state once.
Algorithm.
1. use bit operations to get the initial state;
2. init all states to be Integer.MAX_VALUE and initial state to have cost 0.
3. add the initial state to queue and do bfs: if reaching state 0, return its min cost; otherwise try all possible flips at different cells by using xor operations, if the next state is computed the first time, add it to the queue.
4. return -1 if all states exhausted without success.
The runtime is also O(2^(m * n)), but it is faster thanks to the clear bit operations.
class Solution { public int minFlips(int[][] mat) { int m = mat.length; int n = mat[0].length; int start = 0; for(int i = 0; i < m * n; ++i) start += mat[i / n][i % n] << i; int[] dist = new int[1<<(m * n)]; Arrays.fill(dist, Integer.MAX_VALUE); dist[start] = 0; ArrayDeque<Integer> q = new ArrayDeque<>(); q.add(start); while(!q.isEmpty()) { int curr = q.pollFirst(); int d = dist[curr]; if(curr == 0) { return d; } for (int i = 0; i < m * n; ++i) { int x = i / n; int y = i % n; int next = curr; next ^= 1 << i; if(y > 0) { next ^= 1 << (i - 1); } if(y < n - 1) { next ^= 1 << (i + 1); } if(x > 0) { next ^= 1 << (i - n); } if(x < m - 1) { next ^= 1 << (i + n); } if(d + 1 < dist[next]) { dist[next] = d+1; q.add(next); } } } return -1; } }