LeetCode 3882 - Minimum XOR Path in a Grid
We are given an m × n grid of integers. Starting from the top-left cell (0, 0), we must reach the bottom-right cell (m - 1, n - 1) by moving only right or down.
Difficulty: 🟡 Medium
Topics: Array, Dynamic Programming, Bit Manipulation, Matrix
Solution
LeetCode 3882 - Minimum XOR Path in a Grid
Problem Understanding
We are given an m × n grid of integers. Starting from the top-left cell (0, 0), we must reach the bottom-right cell (m - 1, n - 1) by moving only right or down.
For every path, we compute its cost as the bitwise XOR of all values encountered along the path, including both the starting and ending cells.
Our goal is to find the smallest XOR value achievable among all valid paths.
A key detail is that XOR behaves very differently from addition. With sums, dynamic programming often works by keeping only the minimum value seen so far. For XOR, a path with a larger intermediate XOR can eventually produce a smaller final XOR after more values are XORed in. Therefore, we cannot simply store a single best value per cell.
The constraints provide an important clue:
m * n <= 10000 <= grid[i][j] <= 1023
Since every cell value is at most 1023, all values fit within 10 bits.
When XORing numbers that each use at most 10 bits, the result also remains within the range:
0 ... 1023
This means there are only 1024 possible XOR states.
Although the grid may contain up to 1000 cells, the number of possible XOR values is very small. This strongly suggests a dynamic programming solution that tracks reachable XOR values at each cell.
Important edge cases include:
- A grid containing only one cell.
- A single row grid where only right moves are possible.
- A single column grid where only down moves are possible.
- Multiple paths producing the same XOR value.
- Cases where the minimum XOR is
0. - Large grids where many different XOR states become reachable.
Approaches
Brute Force
A straightforward solution is to enumerate every valid path from the top-left corner to the bottom-right corner.
For each path:
- Traverse the path.
- XOR all visited values.
- Track the minimum XOR encountered.
This approach is correct because it explicitly evaluates every possible path.
However, the number of paths is:
$$\binom{m+n-2}{m-1}$$
For a grid containing hundreds of cells, this number becomes astronomically large. Therefore brute force is completely infeasible.
Key Insight
The crucial observation is that the XOR value can only be one of 1024 possibilities.
Instead of storing a single value for each cell, we store all XOR values that can reach that cell.
Let:
dp[i][j]
be the set of XOR values achievable when arriving at cell (i, j).
If we know all XOR values reaching the cell above and the cell to the left, we can generate all XOR values for the current cell:
new_xor = previous_xor XOR grid[i][j]
Because there are only 1024 possible XOR states, the total state space is bounded by:
1000 × 1024
which is manageable.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^(m+n)) | O(m+n) | Enumerates every path |
| Optimal | O(m × n × 1024) | O(m × n × 1024) | DP over XOR states |
Algorithm Walkthrough
Optimal Dynamic Programming
- Create a DP table where each cell stores a set of reachable XOR values.
- Initialize the starting cell:
dp[0][0] = {grid[0][0]}
The only possible XOR at the starting position is the value of that cell itself.
3. Process cells row by row.
4. For each cell (i, j), consider transitions from the cell above.
For every XOR value x reachable at (i-1, j):
x XOR grid[i][j]
becomes reachable at (i, j).
5. Also consider transitions from the cell to the left.
For every XOR value x reachable at (i, j-1):
x XOR grid[i][j]
becomes reachable at (i, j).
6. Continue until every cell has been processed.
7. The bottom-right cell contains every XOR value achievable by a valid path.
8. Return the minimum value contained in:
dp[m-1][n-1]
Why it works
For every cell, the DP set contains exactly the XOR values obtainable by some valid path ending at that cell.
The base case is correct because the starting cell contributes only its own value.
Every valid path reaching a cell must come either from above or from the left. The transition applies the current cell's value via XOR, which precisely matches the path definition.
Therefore, by induction over the processing order, every reachable XOR value is represented and no impossible XOR value is added. The destination cell contains exactly the XOR values of all valid paths, so taking the minimum yields the correct answer.
Python Solution
class Solution:
def minCost(self, grid: list[list[int]]) -> int:
m, n = len(grid), len(grid[0])
dp = [[set() for _ in range(n)] for _ in range(m)]
dp[0][0].add(grid[0][0])
for i in range(m):
for j in range(n):
if i == 0 and j == 0:
continue
current = set()
if i > 0:
for xor_value in dp[i - 1][j]:
current.add(xor_value ^ grid[i][j])
if j > 0:
for xor_value in dp[i][j - 1]:
current.add(xor_value ^ grid[i][j])
dp[i][j] = current
return min(dp[m - 1][n - 1])
Implementation Explanation
The DP table stores a set of reachable XOR values for every cell.
The starting cell is initialized with a single XOR state, namely its own value.
For each remaining cell, we build a new set of reachable XOR values. Every XOR value coming from above or from the left is extended by XORing the current cell value.
Because sets automatically eliminate duplicates, multiple paths producing the same XOR state do not increase memory usage.
After processing the entire grid, the destination cell contains all achievable XOR results. The answer is simply the smallest value in that set.
Go Solution
func minCost(grid [][]int) int {
m := len(grid)
n := 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{})
}
}
dp[0][0][grid[0][0]] = struct{}{}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if i == 0 && j == 0 {
continue
}
if i > 0 {
for xorValue := range dp[i-1][j] {
dp[i][j][xorValue^grid[i][j]] = struct{}{}
}
}
if j > 0 {
for xorValue := range dp[i][j-1] {
dp[i][j][xorValue^grid[i][j]] = struct{}{}
}
}
}
}
ans := 1024
for xorValue := range dp[m-1][n-1] {
if xorValue < ans {
ans = xorValue
}
}
return ans
}
Go-Specific Notes
Go does not have a built-in set type, so a map[int]struct{} is used to represent a set of XOR states.
The empty struct consumes zero storage per value and is the standard Go idiom for implementing sets.
All XOR values remain in the range 0...1023, so integer overflow is never a concern.
Worked Examples
Example 1
grid =
[
[1, 2],
[3, 4]
]
Initial state:
| Cell | Reachable XORs |
|---|---|
| (0,0) | {1} |
Process (0,1):
1 XOR 2 = 3
| Cell | Reachable XORs |
|---|---|
| (0,1) | {3} |
Process (1,0):
1 XOR 3 = 2
| Cell | Reachable XORs |
|---|---|
| (1,0) | {2} |
Process (1,1):
From above:
3 XOR 4 = 7
From left:
2 XOR 4 = 6
| Cell | Reachable XORs |
|---|---|
| (1,1) | {6, 7} |
Answer:
min(6, 7) = 6
Example 2
grid =
[
[6, 7],
[5, 8]
]
| Cell | Reachable XORs |
|---|---|
| (0,0) | {6} |
Process (0,1):
6 XOR 7 = 1
Process (1,0):
6 XOR 5 = 3
Process (1,1):
From above:
1 XOR 8 = 9
From left:
3 XOR 8 = 11
Destination set:
{9, 11}
Answer:
9
Example 3
grid = [[2, 7, 5]]
Start:
{2}
After second cell:
2 XOR 7 = 5
After third cell:
5 XOR 5 = 0
Destination set:
{0}
Answer:
0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m × n × 1024) | Each cell processes at most 1024 XOR states |
| Space | O(m × n × 1024) | Each cell may store up to 1024 reachable XOR values |
The important observation is that XOR results are bounded by the 10-bit value range. Even though the number of paths can be enormous, the number of distinct XOR states at any cell is at most 1024. This converts an exponential path enumeration problem into a polynomial dynamic programming problem.
Test Cases
sol = Solution()
assert sol.minCost([[1, 2], [3, 4]]) == 6 # Example 1
assert sol.minCost([[6, 7], [5, 8]]) == 9 # Example 2
assert sol.minCost([[2, 7, 5]]) == 0 # Example 3
assert sol.minCost([[5]]) == 5 # Single cell grid
assert sol.minCost([[1, 2, 3]]) == (1 ^ 2 ^ 3) # Single row
assert sol.minCost([[1], [2], [3]]) == (1 ^ 2 ^ 3) # Single column
assert sol.minCost([[0, 0], [0, 0]]) == 0 # All zeros
assert sol.minCost([[1023]]) == 1023 # Maximum cell value
assert sol.minCost([[1, 1], [1, 1]]) == 1 # Multiple paths same result
assert sol.minCost([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]) >= 0 # General larger example
Test Summary
| Test | Why |
|---|---|
[[1,2],[3,4]] |
First official example |
[[6,7],[5,8]] |
Second official example |
[[2,7,5]] |
Single path example |
[[5]] |
Minimum grid size |
| Single row | Only right moves allowed |
| Single column | Only down moves allowed |
| All zeros | Minimum XOR should remain zero |
[[1023]] |
Maximum value boundary |
[[1,1],[1,1]] |
Duplicate XOR states |
| 3×3 grid | General multi-path scenario |
Edge Cases
Single Cell Grid
When m = n = 1, the start and destination are the same cell. No movement occurs, and the path consists of exactly one value. A common bug is attempting transitions from non-existent neighbors. The implementation handles this by initializing dp[0][0] and skipping further processing for that cell.
Single Row or Single Column
In a single row, every path must move right. In a single column, every path must move down. Some implementations incorrectly assume both predecessors always exist. The solution explicitly checks i > 0 and j > 0 before accessing neighbors, so these boundary cases work naturally.
Many Different Paths Producing the Same XOR
Different paths can lead to identical XOR values. If duplicate states are stored repeatedly, memory usage can grow unnecessarily. Using sets ensures that each XOR value is stored only once per cell, keeping the state space bounded by 1024.
Minimum XOR Equal to Zero
A path XOR of zero is entirely possible and is actually the best possible answer because XOR values are non-negative. Some greedy approaches might miss such paths because they focus on locally small XOR values. The DP state representation preserves every reachable XOR value, guaranteeing that a zero result will be found whenever it exists.
Large Grids with Many Paths
Even though a grid may have an enormous number of valid paths, the XOR range remains limited to 1024 states. The implementation exploits this property, preventing the exponential explosion that would occur with path enumeration and ensuring the algorithm remains efficient within the given constraints.