LeetCode 2510 - Check if There is a Path With Equal Number of 0's And 1's
This problem asks us to determine if there exists a path in a binary matrix from the top-left corner (0, 0) to the bottom-right corner (m - 1, n - 1) such that the number of 0s visited along the path is equal to the number of 1s.
Difficulty: 🟡 Medium
Topics: Array, Dynamic Programming, Matrix
Solution
Problem Understanding
This problem asks us to determine if there exists a path in a binary matrix from the top-left corner (0, 0) to the bottom-right corner (m - 1, n - 1) such that the number of 0s visited along the path is equal to the number of 1s. The matrix is binary, so each cell contains either 0 or 1. Movement is restricted to either down or right, which defines the possible paths.
The input is a 2D list grid of dimensions m x n, where 2 <= m, n <= 100. The output is a boolean value: True if a valid path exists, False otherwise.
Important considerations include:
- Paths cannot move diagonally or backward, limiting the search space but still potentially exponential in size for brute-force solutions.
- The matrix size is moderate (up to
100 x 100), which discourages naive DFS that explores all paths without pruning. - Since we are counting
0s and1s, the difference between the counts along a path is an important state to track.
Edge cases that can trip up naive implementations include grids where the total number of cells is odd (impossible to split equally between 0s and 1s), or grids where movement is constrained in such a way that certain counts are impossible to achieve.
Approaches
Brute Force Approach
The brute-force method explores all possible paths from (0, 0) to (m - 1, n - 1) recursively. At each step, we track the number of 0s and 1s visited so far. When we reach the destination, we check if the counts are equal. While this guarantees correctness, it has exponential time complexity O(2^(m+n)) because each cell can branch into two directions. It also requires maintaining state for each path, leading to high memory usage.
Optimal Approach
The key insight is that the problem can be transformed into a dynamic programming problem where the state is (row, col, diff), with diff = count_of_ones - count_of_zeros. At each cell, we only need to track which differences have been achieved along any path to that cell. By propagating these differences using a set per cell, we can efficiently avoid exploring redundant paths with the same diff. This reduces the search space significantly because diff is bounded by -(m*n) to m*n, but practically only feasible differences matter as we move down or right.
Comparison of approaches:
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^(m+n)) | O(m+n) recursion depth | Explores all paths recursively; too slow for m, n up to 100 |
| Optimal | O(m * n * min(m*n, total_diff_range)) | O(m * n * possible_diffs) | Uses dynamic programming with sets to track differences |
Algorithm Walkthrough
- Initialize a 2D array
dpof sizem x nwhere each element is a set that stores the possiblediffvalues at that cell. - At
(0, 0), initializediff = 1ifgrid[0][0] == 1else-1. - Iterate through each cell
(i, j)in the matrix row by row:
- For each possible
diffindp[i-1][j](from top) ordp[i][j-1](from left), compute the newdiffby adding1ifgrid[i][j] == 1or-1if0. - Add the new
difftodp[i][j].
- After filling the DP table, check if
0exists indp[m-1][n-1]. If yes, there is a path with equal number of0s and1s. - Return
Trueif0exists,Falseotherwise.
Why it works: By propagating all feasible differences (count_ones - count_zeros) through the DP table, we ensure that we consider all possible paths without revisiting identical states. The invariant is that dp[i][j] always contains every difference achievable by some path to that cell.
Python Solution
from typing import List
class Solution:
def isThereAPath(self, grid: List[List[int]]) -> bool:
m, n = len(grid), len(grid[0])
dp = [[set() for _ in range(n)] for _ in range(m)]
dp[0][0].add(1 if grid[0][0] == 1 else -1)
for i in range(m):
for j in range(n):
if i == 0 and j == 0:
continue
current_set = set()
if i > 0:
for diff in dp[i-1][j]:
current_set.add(diff + (1 if grid[i][j] == 1 else -1))
if j > 0:
for diff in dp[i][j-1]:
current_set.add(diff + (1 if grid[i][j] == 1 else -1))
dp[i][j] = current_set
return 0 in dp[m-1][n-1]
Explanation: The code initializes a DP table of sets to store differences at each cell. We iterate through the matrix and propagate differences from top and left neighbors, updating the current cell's set. Finally, we check if 0 is reachable at the destination.
Go Solution
func isThereAPath(grid [][]int) bool {
m, n := len(grid), len(grid[0])
dp := make([][]map[int]struct{}, m)
for i := 0; i < m; i++ {
dp[i] = make([]map[int]struct{}, n)
for j := 0; j < n; j++ {
dp[i][j] = make(map[int]struct{})
}
}
startDiff := -1
if grid[0][0] == 1 {
startDiff = 1
}
dp[0][0][startDiff] = struct{}{}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if i == 0 && j == 0 {
continue
}
currSet := make(map[int]struct{})
if i > 0 {
for diff := range dp[i-1][j] {
newDiff := diff
if grid[i][j] == 1 {
newDiff += 1
} else {
newDiff -= 1
}
currSet[newDiff] = struct{}{}
}
}
if j > 0 {
for diff := range dp[i][j-1] {
newDiff := diff
if grid[i][j] == 1 {
newDiff += 1
} else {
newDiff -= 1
}
currSet[newDiff] = struct{}{}
}
}
dp[i][j] = currSet
}
}
_, exists := dp[m-1][n-1][0]
return exists
}
Go-specific notes: We use map[int]struct{} to mimic a set, since Go does not have a native set type. Struct{} is used as a memory-efficient placeholder.
Worked Examples
Example 1: grid = [[0,1,0,0],[0,1,0,0],[1,0,1,0]]
| Step | Cell | Propagated diff set |
|---|---|---|
| Start | (0,0) | {-1} |
| (0,1) | right | {-1 + 1} = {0} |
| (1,0) | down | {-1 + -1} = {-2} |
| ... | ... | ... |
| End | (2,3) | {0, others} → contains 0 → True |
Example 2: grid = [[1,1,0],[0,0,1],[1,0,0]]
DP propagates differences, but (2,2) does not contain 0 → False
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m * n * D) | D is the maximum range of achievable differences at a cell, bounded by m*n |
| Space | O(m * n * D) | Each cell stores a set of differences |
The dynamic programming approach reduces the exponential search space to a polynomial-time algorithm by tracking only achievable differences.
Test Cases
sol = Solution()
# Provided examples
assert sol.isThereAPath([[0,1,0,0],[0,1,0,0],[1,0,1,0]]) == True # path exists
assert sol.isThereAPath([[1,1,0],[0,0,1],[1,0,0]]) == False # no path
# Edge cases
assert sol.isThereAPath([[0,1],[1,0]]) == True # minimal grid with valid path
assert sol.isThereAPath([[0,0],[0,0]]) == False # all zeros, impossible
assert sol.isThereAPath([[1,