LeetCode 2556 - Disconnect Path in a Binary Matrix by at Most One Flip
The problem is asking whether it is possible to disconnect a path from the top-left corner (0, 0) to the bottom-right corner (m - 1, n - 1) in a binary matrix by flipping at most one cell from 1 to 0 or 0 to 1. A path only allows moves down or right into cells containing 1.
Difficulty: 🟡 Medium
Topics: Array, Dynamic Programming, Depth-First Search, Breadth-First Search, Matrix
Solution
Problem Understanding
The problem is asking whether it is possible to disconnect a path from the top-left corner (0, 0) to the bottom-right corner (m - 1, n - 1) in a binary matrix by flipping at most one cell from 1 to 0 or 0 to 1. A path only allows moves down or right into cells containing 1. The matrix is already guaranteed to have 1 in both (0, 0) and (m - 1, n - 1).
The input is an m x n binary matrix with up to 10^5 cells in total. The output is a boolean: true if it is possible to disconnect the matrix with one flip, and false otherwise.
Key constraints include that only one cell can be flipped, and the start and end cells cannot be flipped. The problem size hints that a brute-force attempt to try flipping every cell and checking connectivity would likely be too slow.
Important edge cases include small matrices (1x1 or 1xN), matrices that are already disconnected, and matrices where only critical cells control the unique path. Because movement is restricted to right and down, the number of unique paths may be limited, which is crucial to the optimal approach.
Approaches
Brute Force
A naive approach is to iterate over every cell (i, j) that is not (0, 0) or (m - 1, n - 1), flip its value, and then perform a DFS or BFS to see if (m - 1, n - 1) is still reachable from (0, 0). If we find a flip that disconnects the path, we return true. If none work, we return false.
While correct, this is too slow for large matrices. Each connectivity check is O(m * n), and flipping each cell would result in O(m * n * m * n) time in the worst case, which can reach 10^10 operations - clearly infeasible.
Optimal Approach
The key insight is that if there are two or more independent paths from start to end, removing one cell will not disconnect the matrix. Therefore, to disconnect with a single flip, the grid must have a unique path from (0, 0) to (m - 1, n - 1) where all intermediate cells are critical.
We can achieve this by performing two DFS/BFS traversals:
- Forward DFS from
(0, 0)to record which cells are reachable. - Backward DFS from
(m - 1, n - 1)to record which cells can reach the end.
If there is a cell that is on every valid path (i.e., visited in both forward and backward traversal), then flipping it disconnects the path. We only need to check intermediate cells, not the start or end.
This approach works efficiently in O(m * n) time because each cell is processed at most twice.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O((m * n)^2) | O(m * n) | Flip each cell and check connectivity |
| Optimal | O(m * n) | O(m * n) | DFS/BFS from start and end to identify unique critical path |
Algorithm Walkthrough
- Forward DFS: Start from
(0, 0)and mark all cells reachable moving only down or right through cells with1. Store results in areachable_from_startmatrix. - Backward DFS: Start from
(m - 1, n - 1)and mark all cells that can reach the end moving only up or left through cells with1. Store results in areachable_to_endmatrix. - Check critical cells: Iterate through all intermediate cells
(i, j)excluding(0, 0)and(m - 1, n - 1). Ifreachable_from_start[i][j]andreachable_to_end[i][j]are both true, this cell lies on a unique path. If such a cell exists, flipping it disconnects the path, so returntrue. - No critical cell found: If no intermediate cell is on the unique path, return
false.
Why it works: The forward and backward traversals together identify all cells that are part of some path. A cell visited in both is critical. Since flipping one critical cell disconnects the unique path, checking all such cells guarantees correctness.
Python Solution
from typing import List
class Solution:
def isPossibleToCutPath(self, grid: List[List[int]]) -> bool:
m, n = len(grid), len(grid[0])
# Forward DFS from (0,0)
reachable_from_start = [[False]*n for _ in range(m)]
reachable_from_start[0][0] = True
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
if i > 0 and reachable_from_start[i-1][j]:
reachable_from_start[i][j] = True
if j > 0 and reachable_from_start[i][j-1]:
reachable_from_start[i][j] = True
# Backward DFS from (m-1,n-1)
reachable_to_end = [[False]*n for _ in range(m)]
reachable_to_end[m-1][n-1] = True
for i in range(m-1, -1, -1):
for j in range(n-1, -1, -1):
if grid[i][j] == 1:
if i < m-1 and reachable_to_end[i+1][j]:
reachable_to_end[i][j] = True
if j < n-1 and reachable_to_end[i][j+1]:
reachable_to_end[i][j] = True
# Check intermediate critical cells
for i in range(m):
for j in range(n):
if (i == 0 and j == 0) or (i == m-1 and j == n-1):
continue
if reachable_from_start[i][j] and reachable_to_end[i][j]:
return True
return False
The Python code constructs two boolean matrices to track reachable cells from the start and cells that can reach the end. It iterates through the grid twice: once forward and once backward. Any intermediate cell that is part of both matrices is critical. Flipping it disconnects the path.
Go Solution
func isPossibleToCutPath(grid [][]int) bool {
m, n := len(grid), len(grid[0])
reachableFromStart := make([][]bool, m)
reachableToEnd := make([][]bool, m)
for i := 0; i < m; i++ {
reachableFromStart[i] = make([]bool, n)
reachableToEnd[i] = make([]bool, n)
}
reachableFromStart[0][0] = true
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 1 {
if i > 0 && reachableFromStart[i-1][j] {
reachableFromStart[i][j] = true
}
if j > 0 && reachableFromStart[i][j-1] {
reachableFromStart[i][j] = true
}
}
}
}
reachableToEnd[m-1][n-1] = true
for i := m-1; i >= 0; i-- {
for j := n-1; j >= 0; j-- {
if grid[i][j] == 1 {
if i < m-1 && reachableToEnd[i+1][j] {
reachableToEnd[i][j] = true
}
if j < n-1 && reachableToEnd[i][j+1] {
reachableToEnd[i][j] = true
}
}
}
}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if (i == 0 && j == 0) || (i == m-1 && j == n-1) {
continue
}
if reachableFromStart[i][j] && reachableToEnd[i][j] {
return true
}
}
}
return false
}
The Go implementation mirrors Python but uses slices of slices for the boolean matrices. Iteration and bounds checks are handled explicitly, and the logic for marking reachable cells is identical.
Worked Examples
Example 1: [[1,1,1],[1,0,0],[1,1,1]]
Forward reachability:
T T T
T F F
T T T
Backward reachability:
T F F
F F F
T T T
Intermediate critical cell: (1,0) is both forward and backward reachable, so flipping it disconnects the path. Output: true.
Example 2: [[1,1,1],[1,0,1],[1,1,1]]
Forward reachability:
T T