LeetCode 1036 - Escape a Large Maze
The problem gives us a massive 1,000,000 x 1,000,000 grid. Each cell is identified by coordinates (x, y), and movement is allowed in four directions: up, down, left, and right. Some cells are blocked, meaning we cannot step onto them.
Difficulty: 🔴 Hard
Topics: Array, Hash Table, Depth-First Search, Breadth-First Search
Solution
Problem Understanding
The problem gives us a massive 1,000,000 x 1,000,000 grid. Each cell is identified by coordinates (x, y), and movement is allowed in four directions: up, down, left, and right. Some cells are blocked, meaning we cannot step onto them.
We are given three inputs:
blocked, a list of blocked coordinatessource, the starting coordinatetarget, the destination coordinate
The goal is to determine whether there exists any valid path from source to target without stepping outside the grid or onto blocked cells.
At first glance, this looks like a standard graph traversal problem where each cell is a node and adjacent cells are connected by edges. Normally, Breadth-First Search or Depth-First Search would solve this easily.
However, the constraints fundamentally change the problem:
- The grid contains up to
10^12cells blocked.length <= 200
The grid is astronomically large, which makes traversing the entire space impossible. A naive BFS over the grid would never finish in time or memory.
The important observation is that although the grid is huge, the number of blocked cells is extremely small. With at most 200 blocked cells, the blocked region can only enclose a limited area.
Several edge cases are important:
- The source may be completely trapped by blocked cells
- The target may be completely trapped
- There may be no blocked cells at all
- The source and target may be extremely far apart
- The blocked cells may form partial walls that look dangerous but do not actually enclose anything
The problem guarantees that neither source nor target is blocked, and that they are distinct coordinates.
Approaches
Brute Force Approach
The brute force idea is straightforward:
- Start BFS from
source - Explore all reachable cells
- Return
trueif we eventually reachtarget - Return
falseif BFS exhausts all possibilities
This works because BFS explores every reachable state exactly once, guaranteeing correctness.
Unfortunately, this approach is completely infeasible.
The grid contains 10^12 cells. Even if the path is short, BFS may need to explore enormous portions of the grid. Memory usage alone would become impossible.
The key issue is that the search space is far too large.
Key Insight
The crucial observation is that there are only at most 200 blocked cells.
A small number of blocked cells cannot surround an arbitrarily large region. In fact, the maximum enclosed area is bounded.
If we can prove that a search escapes the potentially enclosed region around the source, then the source is not trapped. The same logic applies to the target.
This transforms the problem:
We do not need to search the entire grid.
We only need to determine whether either endpoint is enclosed by blocked cells.
For n blocked cells, the maximum enclosed area is approximately:
$\frac{n(n-1)}{2}$
With n <= 200, this value is at most 19,900.
Therefore:
- If BFS expands beyond this limit, we know the start point is not enclosed
- If both source and target are not enclosed, a path must exist between them in the open grid
This allows a bounded BFS that terminates quickly.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(10^12) | O(10^12) | Attempts to explore the massive grid directly |
| Optimal | O(B²) | O(B²) | Uses the bounded enclosure property of blocked cells |
Here, B = len(blocked).
Algorithm Walkthrough
Step 1: Convert blocked cells into a hash set
We store all blocked coordinates in a hash set for constant time lookup.
This is important because during BFS we constantly need to check whether a neighboring cell is blocked.
A tuple like (x, y) is used as the key.
Step 2: Compute the maximum possible enclosed area
If there are n blocked cells, the largest region they can fully surround is:
$\frac{n(n-1)}{2}$
This value becomes our BFS exploration limit.
If BFS visits more than this many cells, then the search has escaped any possible enclosure.
Step 3: Run bounded BFS from the source
We perform BFS starting at source.
For each position:
- Remove one cell from the queue
- Check its four neighbors
- Ignore neighbors that:
- are outside the grid
- are blocked
- were already visited
- Add valid neighbors into the queue
The BFS stops early in two cases:
- We reach the target directly
- We visit more cells than the enclosure limit
If we exceed the limit, we know the source is not trapped.
Step 4: Run bounded BFS from the target
We repeat the same process starting from target.
This step is necessary because:
- The source might not be enclosed
- But the target still could be enclosed
Both endpoints must be capable of escaping their local region.
Step 5: Combine the results
The final answer is:
trueif both searches succeedfalseotherwise
A search succeeds if:
- It reaches the other endpoint
- Or it escapes the maximum enclosure boundary
Why it works
The correctness relies on a geometric property:
With at most 200 blocked cells, there is a strict upper bound on the area they can fully enclose.
If BFS expands beyond that bound, then the search must have escaped any possible trapped region.
Since the infinite open area outside enclosed regions is connected, if both source and target are not trapped, a path between them must exist.
Python Solution
from collections import deque
from typing import List, Set, Tuple
class Solution:
def isEscapePossible(
self,
blocked: List[List[int]],
source: List[int],
target: List[int]
) -> bool:
BLOCK_LIMIT = len(blocked) * (len(blocked) - 1) // 2
GRID_SIZE = 10**6
blocked_set: Set[Tuple[int, int]] = {
(x, y) for x, y in blocked
}
directions = [
(1, 0),
(-1, 0),
(0, 1),
(0, -1)
]
def bfs(start: List[int], finish: List[int]) -> bool:
queue = deque()
queue.append((start[0], start[1]))
visited = set()
visited.add((start[0], start[1]))
while queue and len(visited) <= BLOCK_LIMIT:
x, y = queue.popleft()
if [x, y] == finish:
return True
for dx, dy in directions:
nx = x + dx
ny = y + dy
if not (0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE):
continue
if (nx, ny) in blocked_set:
continue
if (nx, ny) in visited:
continue
visited.add((nx, ny))
queue.append((nx, ny))
return len(visited) > BLOCK_LIMIT
return bfs(source, target) and bfs(target, source)
The implementation begins by converting the blocked cells into a set for fast lookup. Since BFS repeatedly checks whether neighboring cells are blocked, constant time membership checks are essential for performance.
The variable BLOCK_LIMIT represents the maximum possible enclosed area formed by the blocked cells. This value determines when BFS can safely conclude that it has escaped confinement.
The helper function bfs performs a bounded breadth-first search. It explores neighboring cells while maintaining a visited set to prevent revisiting states.
The key optimization appears in the loop condition:
while queue and len(visited) <= BLOCK_LIMIT:
Once the number of visited cells exceeds the enclosure limit, the search immediately succeeds because the start point cannot be trapped.
The algorithm performs BFS twice:
- from source to target
- from target to source
This guarantees that neither endpoint is enclosed.
Go Solution
package main
import "container/list"
func isEscapePossible(blocked [][]int, source []int, target []int) bool {
blockLimit := len(blocked) * (len(blocked) - 1) / 2
gridSize := 1000000
blockedSet := make(map[[2]int]bool)
for _, cell := range blocked {
blockedSet[[2]int{cell[0], cell[1]}] = true
}
directions := [][2]int{
{1, 0},
{-1, 0},
{0, 1},
{0, -1},
}
bfs := func(start []int, finish []int) bool {
queue := list.New()
queue.PushBack([2]int{start[0], start[1]})
visited := make(map[[2]int]bool)
visited[[2]int{start[0], start[1]}] = true
for queue.Len() > 0 && len(visited) <= blockLimit {
front := queue.Front()
queue.Remove(front)
cell := front.Value.([2]int)
x, y := cell[0], cell[1]
if x == finish[0] && y == finish[1] {
return true
}
for _, d := range directions {
nx := x + d[0]
ny := y + d[1]
if nx < 0 || nx >= gridSize || ny < 0 || ny >= gridSize {
continue
}
next := [2]int{nx, ny}
if blockedSet[next] {
continue
}
if visited[next] {
continue
}
visited[next] = true
queue.PushBack(next)
}
}
return len(visited) > blockLimit
}
return bfs(source, target) && bfs(target, source)
}
The Go implementation follows the same logic as the Python version, but there are a few language-specific details.
Go does not have built-in tuple hashing, so fixed-size arrays like [2]int are used as hash map keys.
The BFS queue is implemented using container/list, since Go slices are inefficient for repeated front removals.
The visited set and blocked set are both implemented as maps from [2]int to bool.
Integer overflow is not an issue because all calculations remain well within Go's integer range.
Worked Examples
Example 1
blocked = [[0,1],[1,0]]
source = [0,0]
target = [0,2]
Blocked layout near the source:
| Coordinate | Status |
|---|---|
| (0,0) | Source |
| (0,1) | Blocked |
| (1,0) | Blocked |
The source is trapped in the corner.
BFS from source
| Step | Current Cell | Queue | Visited |
|---|---|---|---|
| 1 | (0,0) | [] | {(0,0)} |
Neighbors checked:
| Neighbor | Result |
|---|---|
| (-1,0) | Outside grid |
| (1,0) | Blocked |
| (0,-1) | Outside grid |
| (0,1) | Blocked |
No valid moves exist.
The queue becomes empty before escaping the enclosure limit.
Result:
false
Example 2
blocked = []
source = [0,0]
target = [999999,999999]
There are no blocked cells.
The enclosure limit becomes:
$\frac{0(0-1)}{2}=0$
The BFS immediately exceeds the limit after visiting the first cell.
BFS behavior
| Step | Visited Size | Condition |
|---|---|---|
| Start | 1 | 1 > 0 |
This proves the source is not enclosed.
The same logic applies to the target.
Result:
true
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(B²) | Each BFS explores at most the maximum enclosed area |
| Space | O(B²) | The visited set stores at most the bounded search region |
Here:
B = len(blocked)B <= 200
The maximum enclosed region formed by B blocked cells is bounded by:
$\frac{B(B-1)}{2}$
Since B <= 200, the algorithm explores at most about 20,000 cells per BFS. This makes the solution extremely efficient despite the enormous grid size.
Test Cases
sol = Solution()
# Example 1, source trapped immediately
assert sol.isEscapePossible(
[[0, 1], [1, 0]],
[0, 0],
[0, 2]
) is False
# Example 2, completely open grid
assert sol.isEscapePossible(
[],
[0, 0],
[999999, 999999]
) is True
# Single blocked cell does not matter
assert sol.isEscapePossible(
[[10, 10]],
[0, 0],
[999999, 999999]
) is True
# Target enclosed
assert sol.isEscapePossible(
[[0, 1], [1, 0], [1, 2], [2, 1]],
[10, 10],
[1, 1]
) is False
# Source enclosed
assert sol.isEscapePossible(
[[0, 1], [1, 0], [1, 2], [2, 1]],
[1, 1],
[10, 10]
) is False
# Large coordinates near boundary
assert sol.isEscapePossible(
[],
[999998, 999998],
[999999, 999999]
) is True
# Partial wall that does not fully enclose
assert sol.isEscapePossible(
[[0, 1], [1, 1], [2, 1]],
[0, 0],
[3, 0]
) is True
# Small enclosed pocket
assert sol.isEscapePossible(
[[1, 0], [0, 1], [1, 2], [2, 1]],
[1, 1],
[3, 3]
) is False
# Sparse blocked cells across grid
assert sol.isEscapePossible(
[[100, 100], [200, 200], [300, 300]],
[0, 0],
[999999, 999999]
) is True
# Source and target close together
assert sol.isEscapePossible(
[],
[5, 5],
[5, 6]
) is True
Test Summary
| Test | Why |
|---|---|
| Example 1 | Validates immediate trapping at grid corner |
| Example 2 | Validates empty blocked list |
| Single blocked cell | Ensures isolated blocks do not matter |
| Target enclosed | Confirms target-side BFS is necessary |
| Source enclosed | Confirms source-side BFS works |
| Boundary coordinates | Tests grid edge handling |
| Partial wall | Ensures incomplete barriers do not fail |
| Small enclosed pocket | Validates true enclosure detection |
| Sparse blocked cells | Confirms large open traversal succeeds |
| Adjacent source/target | Tests trivial reachable path |
Edge Cases
Source Trapped at the Boundary
A particularly tricky case occurs when the source is near the edge of the grid and blocked cells cover the only valid exits.
For example:
source = [0,0]
blocked = [[0,1],[1,0]]
A careless implementation might assume there are always four neighboring moves available. However, two directions are invalid because they leave the grid.
The implementation correctly checks boundary conditions before exploring neighbors:
0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE
This prevents invalid positions from entering the BFS queue.
Target Completely Enclosed
Another subtle issue is that the source may escape successfully while the target remains trapped.
If we only run BFS from the source, we could incorrectly conclude that escape is possible.
For example:
source = open area
target = enclosed pocket
The solution avoids this bug by running bounded BFS from both endpoints.
Only if both searches confirm escape do we return true.
No Blocked Cells
When blocked is empty, the enclosure limit becomes zero.
A naive implementation could accidentally terminate immediately without exploring anything.
The algorithm handles this naturally:
- The starting cell itself counts as visited
len(visited) > 0- Therefore the BFS immediately proves the start is not enclosed
This correctly returns true for any two distinct coordinates in an open grid.