2556. Disconnect Path in a Binary Matrix by at Most One Flip
Problem Description
You have a binary matrix (containing only 0s and 1s) where you can move from any cell to the cell directly below it or to the right of it, but only if that cell contains a 1
. Your starting position is the top-left corner (0, 0)
and your goal is to reach the bottom-right corner (m-1, n-1)
.
The matrix is considered "disconnected" if there's no valid path from the start to the end position.
Your task is to determine if you can make the matrix disconnected by flipping at most one cell (changing a 0
to 1
or a 1
to 0
). However, you're not allowed to flip the starting cell (0, 0)
or the ending cell (m-1, n-1)
.
Return true
if it's possible to disconnect the matrix with at most one flip, otherwise return false
.
The solution uses a clever approach: it runs DFS twice to find paths from start to end. In the first traversal, it marks visited cells by setting them to 0
, effectively removing one path. If a second path still exists after the first one is removed, it means there are at least two disjoint paths, and flipping a single cell won't disconnect the matrix. The function returns true
only when both traversals don't find valid paths (meaning the matrix can be disconnected).
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The binary matrix represents a graph where each cell with value
1
is a node, and edges exist between adjacent cells (right and down directions). We need to find paths from source(0, 0)
to destination(m-1, n-1)
.
Is it a tree?
- No: The graph structure is not a tree. There can be multiple paths from the source to destination, and cycles may exist (though we only move right and down, the overall structure isn't hierarchical like a tree).
Is the problem related to directed acyclic graphs?
- No: While our movement is directed (only right and down), the main focus isn't on DAG properties like topological ordering. The problem is about path connectivity.
Is the problem related to shortest paths?
- No: We don't need to find the shortest path. We need to determine if we can disconnect all paths by flipping at most one cell.
Does the problem involve connectivity?
- Yes: The core of the problem is about connectivity - specifically, whether we can break the connectivity between source and destination by flipping one cell. We need to check if paths exist and if they can be disconnected.
Does the problem have small constraints?
- Yes: Although not explicitly stated, matrix problems typically have manageable constraints that allow DFS traversal without timeout issues.
DFS/backtracking?
- Yes: DFS is perfect for exploring paths in the matrix and determining connectivity. The solution uses DFS twice to check for disjoint paths.
Conclusion: The flowchart leads us to use DFS for solving this connectivity problem. The solution cleverly uses two DFS traversals - the first one marks a path (by setting visited cells to 0), and the second checks if an alternative path exists. If both traversals succeed, there are at least two disjoint paths, making it impossible to disconnect with a single flip.
Intuition
To disconnect a path from (0, 0)
to (m-1, n-1)
by flipping at most one cell, we need to think about what makes a path "cuttable" with a single flip.
Consider this key insight: if there are two completely disjoint paths from start to end, then no single cell flip can disconnect both paths simultaneously. Why? Because flipping one cell can only affect paths that pass through that specific cell. If two paths don't share any cells (except the start and end points), cutting one path leaves the other intact.
On the flip side, if all paths from start to end share at least one common cell (a "bottleneck"), then flipping that bottleneck cell to 0
would disconnect the entire matrix.
But how do we check if there are two disjoint paths? Here's the clever trick:
- First, find any path from start to end using DFS, and mark all visited cells as
0
(essentially "using up" this path) - Then, restore only the start and end cells back to
1
(since we can't flip these anyway) - Try to find another path using DFS again
If the second DFS succeeds, it means there exists a second path that doesn't use any cells from the first path (except start and end). This proves there are at least two disjoint paths, making it impossible to disconnect with a single flip.
If either DFS fails, it means:
- The first DFS failing: already disconnected (return
true
) - Only the second DFS failing: all paths share common cells from the first path, so we can disconnect by flipping one of those bottleneck cells (return
true
) - Both succeed: two disjoint paths exist (return
false
)
This is why the solution returns not (a and b)
- we can disconnect the matrix only when we don't have two successful disjoint paths.
Learn more about Depth-First Search, Breadth-First Search and Dynamic Programming patterns.
Solution Approach
The implementation uses two DFS traversals to determine if there are two disjoint paths from (0, 0)
to (m-1, n-1)
.
First DFS Traversal:
The function dfs(i, j)
explores paths from position (i, j)
to the destination (m-1, n-1)
:
- Base cases: Return
False
if out of bounds (i >= m
orj >= n
) or if the current cell is0
- Mark the current cell as visited by setting
grid[i][j] = 0
to prevent revisiting - Check if we've reached the destination: if
i == m-1
andj == n-1
, returnTrue
- Recursively explore two directions: down
(i+1, j)
and right(i, j+1)
- Return
True
if either direction leads to the destination
The first call a = dfs(0, 0)
finds one path and marks all cells along this path as 0
.
Grid Reset: After the first traversal, we restore the start and end cells:
grid[0][0] = grid[-1][-1] = 1
This is necessary because:
- We need these cells to be
1
for the second traversal to work - These cells cannot be flipped according to the problem constraints
Second DFS Traversal:
The second call b = dfs(0, 0)
attempts to find another path. Since the first DFS marked its path cells as 0
, this second traversal must find a completely different path (if one exists).
Final Decision:
return not (a and b)
:
- If both
a
andb
areTrue
: Two disjoint paths exist, so we cannot disconnect with one flip → returnFalse
- If either is
False
: Either already disconnected or only one path exists → returnTrue
The time complexity is O(m*n)
for each DFS traversal, and space complexity is O(m*n)
for the recursion stack 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 the solution approach.
Consider this 3×3 grid:
1 1 0 1 1 1 0 1 1
Step 1: First DFS Traversal
Starting from (0,0), we explore paths to reach (2,2):
- From (0,0) → move right to (0,1)
- From (0,1) → move down to (1,1)
- From (1,1) → move right to (1,2)
- From (1,2) → move down to (2,2) ✓ Reached destination!
As we traverse, we mark visited cells as 0:
0 0 0 1 0 0 0 1 0
Result: a = True
(path found)
Step 2: Restore Start and End Cells
We restore (0,0) and (2,2) back to 1:
1 0 0 1 0 0 0 1 1
Step 3: Second DFS Traversal
Starting again from (0,0), we try to find another path:
- From (0,0) → can't go right (0,1) is 0
- From (0,0) → move down to (1,0)
- From (1,0) → can't go right (1,1) is 0
- From (1,0) → can't go down (2,0) is 0
- Dead end! Cannot reach (2,2)
Result: b = False
(no second path found)
Step 4: Final Decision
Since a = True
and b = False
:
not (a and b)
=not (True and False)
=not False
=True
The function returns True
, meaning we can disconnect the matrix with one flip. Indeed, flipping any cell on the original path (like (1,1)) would disconnect the grid since all paths must pass through that bottleneck.
Counter-Example: Two Disjoint Paths
Now consider this grid:
1 1 1 1 0 1 1 1 1
First DFS might take: (0,0) → (0,1) → (0,2) → (1,2) → (2,2), marking those cells as 0.
After restoration, second DFS can take: (0,0) → (1,0) → (2,0) → (2,1) → (2,2).
Both paths succeed (a = True
, b = True
), so the function returns False
- we cannot disconnect this grid with a single flip because there are two completely separate paths.
Solution Implementation
1class Solution:
2 def isPossibleToCutPath(self, grid: List[List[int]]) -> bool:
3 """
4 Determines if it's possible to disconnect paths from (0,0) to (m-1,n-1)
5 by removing cells. The algorithm uses DFS twice to check if there are
6 at least 2 vertex-disjoint paths.
7
8 Args:
9 grid: 2D binary matrix where 1 represents passable cell, 0 represents blocked
10
11 Returns:
12 True if paths can be disconnected, False otherwise
13 """
14
15 def dfs(row: int, col: int) -> bool:
16 """
17 Depth-first search to find a path from current position to bottom-right.
18 Marks visited cells as 0 to prevent revisiting.
19
20 Args:
21 row: Current row index
22 col: Current column index
23
24 Returns:
25 True if a path exists to destination, False otherwise
26 """
27 # Check boundaries and if cell is blocked
28 if row >= rows or col >= cols or grid[row][col] == 0:
29 return False
30
31 # Mark current cell as visited by setting it to 0
32 grid[row][col] = 0
33
34 # Check if we reached the destination
35 if row == rows - 1 and col == cols - 1:
36 return True
37
38 # Try moving down or right
39 return dfs(row + 1, col) or dfs(row, col + 1)
40
41 # Get grid dimensions
42 rows, cols = len(grid), len(grid[0])
43
44 # First DFS: Find if any path exists (this will mark one path)
45 first_path_exists = dfs(0, 0)
46
47 # Restore start and end points for second DFS attempt
48 grid[0][0] = 1
49 grid[rows - 1][cols - 1] = 1
50
51 # Second DFS: Try to find another path
52 second_path_exists = dfs(0, 0)
53
54 # If both paths exist, it's not possible to cut (need at least 2 disjoint paths)
55 # If one or no paths exist, it's possible to cut
56 return not (first_path_exists and second_path_exists)
57
1class Solution {
2 private int[][] grid;
3 private int rows;
4 private int cols;
5
6 /**
7 * Determines if it's possible to disconnect the path from top-left to bottom-right
8 * by removing at most one cell (excluding start and end points).
9 *
10 * @param grid 2D binary grid where 1 represents a passable cell and 0 represents a blocked cell
11 * @return true if the path can be disconnected, false otherwise
12 */
13 public boolean isPossibleToCutPath(int[][] grid) {
14 this.grid = grid;
15 this.rows = grid.length;
16 this.cols = grid[0].length;
17
18 // First DFS: Find if a path exists and mark visited cells as 0
19 boolean firstPathExists = dfs(0, 0);
20
21 // Restore start and end cells for the second traversal
22 grid[0][0] = 1;
23 grid[rows - 1][cols - 1] = 1;
24
25 // Second DFS: Check if another path exists after the first path cells are marked
26 boolean secondPathExists = dfs(0, 0);
27
28 // If both paths exist, there are at least 2 disjoint paths, so we cannot disconnect
29 // Return true only if we cannot find two successful paths
30 return !(firstPathExists && secondPathExists);
31 }
32
33 /**
34 * Performs depth-first search to find a path from current position to bottom-right corner.
35 * Marks visited cells as 0 to prevent revisiting.
36 *
37 * @param row current row position
38 * @param col current column position
39 * @return true if a path to destination exists, false otherwise
40 */
41 private boolean dfs(int row, int col) {
42 // Check boundaries and if current cell is blocked
43 if (row >= rows || col >= cols || grid[row][col] == 0) {
44 return false;
45 }
46
47 // Check if we've reached the destination (bottom-right corner)
48 if (row == rows - 1 && col == cols - 1) {
49 return true;
50 }
51
52 // Mark current cell as visited by setting it to 0
53 grid[row][col] = 0;
54
55 // Try moving down or right
56 return dfs(row + 1, col) || dfs(row, col + 1);
57 }
58}
59
1class Solution {
2public:
3 bool isPossibleToCutPath(vector<vector<int>>& grid) {
4 int rows = grid.size();
5 int cols = grid[0].size();
6
7 // Define DFS function to find a path from (row, col) to (rows-1, cols-1)
8 // The function marks visited cells as 0 to avoid revisiting
9 function<bool(int, int)> findPath = [&](int row, int col) -> bool {
10 // Check if current position is out of bounds or blocked
11 if (row >= rows || col >= cols || grid[row][col] == 0) {
12 return false;
13 }
14
15 // Check if we reached the destination
16 if (row == rows - 1 && col == cols - 1) {
17 return true;
18 }
19
20 // Mark current cell as visited by setting it to 0
21 grid[row][col] = 0;
22
23 // Try to move down or right
24 return findPath(row + 1, col) || findPath(row, col + 1);
25 };
26
27 // First attempt: Find if there exists at least one path
28 bool firstPathExists = findPath(0, 0);
29
30 // Restore start and end cells for second attempt
31 grid[0][0] = 1;
32 grid[rows - 1][cols - 1] = 1;
33
34 // Second attempt: Try to find another path after the first path is blocked
35 bool secondPathExists = findPath(0, 0);
36
37 // If both paths exist, it means we cannot disconnect the grid
38 // Return true if we can cut the path (i.e., not both paths exist)
39 return !(firstPathExists && secondPathExists);
40 }
41};
42
1/**
2 * Determines if it's possible to cut the path from top-left to bottom-right
3 * by removing at most one cell from the grid.
4 *
5 * @param grid - 2D array where 1 represents a passable cell and 0 represents blocked
6 * @returns true if the path can be cut, false otherwise
7 */
8function isPossibleToCutPath(grid: number[][]): boolean {
9 const rows: number = grid.length;
10 const cols: number = grid[0].length;
11
12 /**
13 * Performs depth-first search to find a path from current position to bottom-right corner.
14 * Marks visited cells as 0 to avoid revisiting.
15 *
16 * @param row - current row index
17 * @param col - current column index
18 * @returns true if a path exists to the destination, false otherwise
19 */
20 const dfs = (row: number, col: number): boolean => {
21 // Check boundaries and if current cell is passable
22 if (row >= rows || col >= cols || grid[row][col] !== 1) {
23 return false;
24 }
25
26 // Mark current cell as visited by setting it to 0
27 grid[row][col] = 0;
28
29 // Check if we've reached the destination (bottom-right corner)
30 if (row === rows - 1 && col === cols - 1) {
31 return true;
32 }
33
34 // Try moving down or right recursively
35 return dfs(row + 1, col) || dfs(row, col + 1);
36 };
37
38 // First attempt: Find if a path exists (modifies the grid)
39 const firstPathExists: boolean = dfs(0, 0);
40
41 // Restore start and end points for second attempt
42 grid[0][0] = 1;
43 grid[rows - 1][cols - 1] = 1;
44
45 // Second attempt: Check if another path exists after the first traversal
46 const secondPathExists: boolean = dfs(0, 0);
47
48 // If both paths exist, it means there are at least 2 disjoint paths,
49 // so it's not possible to cut the path by removing one cell
50 return !(firstPathExists && secondPathExists);
51}
52
Time and Space Complexity
The time complexity is O(m × n)
, where m
is the number of rows and n
is the number of columns in the grid. This is because the DFS function visits each cell at most once during each traversal. The algorithm performs two DFS traversals from (0, 0)
to (m-1, n-1)
. In each traversal, every reachable cell is visited exactly once and marked as 0 (making it unvisitable for subsequent calls within that traversal). Therefore, the total number of cells visited across both DFS calls is at most 2 × m × n
, which simplifies to O(m × n)
.
The space complexity is O(m × n)
. While the algorithm modifies the input grid in-place and doesn't use additional data structures for storage, the recursive DFS calls consume stack space. In the worst case, the recursion depth can be O(m + n)
when following a path from the top-left to bottom-right corner. However, considering that Python's default recursion limit and the potential for the recursive calls to explore multiple branches before backtracking, the space complexity in the worst case scenario (when considering the call stack for all possible recursive branches that might be explored) is O(m × n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Modifying the Original Grid Without Restoration
The solution modifies the input grid directly during DFS traversal by marking visited cells as 0
. This is a destructive operation that permanently changes the input, which could be problematic if:
- The caller expects the grid to remain unchanged
- You need to use the grid for other operations afterward
- The problem explicitly states not to modify the input
Solution: Create a deep copy of the grid before processing, or use a separate visited tracking mechanism:
def isPossibleToCutPath(self, grid: List[List[int]]) -> bool:
# Create a deep copy to avoid modifying original
grid_copy = [row[:] for row in grid]
# Or alternatively, use a visited set:
def dfs_with_visited(row, col, visited):
if (row, col) in visited or row >= rows or col >= cols or grid[row][col] == 0:
return False
visited.add((row, col))
# ... rest of logic
2. Incorrect Understanding of the Algorithm's Logic
A common misconception is thinking that the algorithm finds ALL paths and then checks if there are exactly two. In reality, it finds at most two vertex-disjoint paths. The first DFS blocks its path, and if a second path still exists, it means there are at least two disjoint paths, making disconnection impossible with a single flip.
Key insight: The algorithm doesn't count paths; it checks if removing one path still leaves another path available.
3. Edge Case: Single Cell or Single Row/Column Grids
For grids with dimensions like 1x1
, 1xn
, or mx1
, the algorithm might not behave as expected because:
- A
1x1
grid has start and end at the same position - Single row/column grids have only one possible path
Solution: Add special handling for edge cases:
def isPossibleToCutPath(self, grid: List[List[int]]) -> bool:
rows, cols = len(grid), len(grid[0])
# Handle edge cases
if rows == 1 and cols == 1:
return False # Can't disconnect a single cell
if rows == 1 or cols == 1:
# Check if there's any intermediate cell with value 1
# If yes, flipping it would disconnect
if rows == 1:
return any(grid[0][i] == 1 for i in range(1, cols-1))
else:
return any(grid[i][0] == 1 for i in range(1, rows-1))
# Continue with regular algorithm...
4. Misunderstanding the Return Logic
The return not (a and b)
logic can be confusing. Remember:
True
means the matrix CAN be disconnected with at most one flipFalse
means it CANNOT be disconnected with just one flip- If both DFS succeed (
a and b
are bothTrue
), there are multiple disjoint paths, so one flip won't disconnect them
5. Stack Overflow for Large Grids
The recursive DFS can cause stack overflow for very large grids due to Python's recursion limit.
Solution: Use iterative DFS with an explicit stack:
def dfs_iterative(start_row, start_col):
stack = [(start_row, start_col)]
while stack:
row, col = stack.pop()
if row >= rows or col >= cols or grid[row][col] == 0:
continue
grid[row][col] = 0
if row == rows - 1 and col == cols - 1:
return True
stack.append((row + 1, col))
stack.append((row, col + 1))
return False
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
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
Want a Structured Path to Master System Design Too? Don’t Miss This!