Facebook Pixel

1036. Escape a Large Maze

Problem Description

You have a very large grid with dimensions 1 million by 1 million on an XY-plane. Each square in the grid has coordinates (x, y).

You need to determine if you can travel from a starting position source = [sx, sy] to a destination position target = [tx, ty]. However, there are some blocked squares that you cannot pass through, given in an array blocked where each blocked[i] = [xi, yi] represents a square at coordinates (xi, yi) that is impassable.

Movement rules:

  • From any square, you can move one step in four directions: north (up), east (right), south (down), or west (left)
  • You cannot move to a blocked square
  • You cannot move outside the grid boundaries (coordinates must stay between 0 and 999,999)

The task is to return true if there exists at least one valid path from the source to the target following these movement rules, and false otherwise.

The key insight is that despite the massive grid size, the number of blocked squares is limited (at most 200). This means that blocked squares can only form a barrier of limited size. If blocked squares try to completely encircle either the source or target, they can only trap an area of at most len(blocked)^2 / 2 squares. The solution leverages this by checking if either point can "escape" from a potential encirclement - if both the source and target can reach enough squares (more than the maximum possible trapped area), then they must be able to reach each other.

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 problem involves a grid where we need to find a path from source to target. A grid can be represented as a graph where each cell is a node and adjacent cells are connected by edges.

Is it a tree?

  • No: The grid structure is not a tree. It's a general graph where cycles can exist (you can move in circles).

Is the problem related to directed acyclic graphs?

  • No: The grid allows movement in all four directions, creating potential cycles, so it's not a DAG.

Is the problem related to shortest paths?

  • No: We only need to determine if a path exists between source and target, not find the shortest one.

Does the problem involve connectivity?

  • Yes: The core question is whether the source and target are connected (can we reach target from source).

Does the problem have small constraints?

  • Yes: While the grid is massive (10^6 Γ— 10^6), the key constraint is that we have at most 200 blocked squares. The solution leverages this small constraint by recognizing that blocked squares can only trap a limited area.

DFS/backtracking?

  • Yes: DFS is perfect here because we need to explore paths and the small number of blocked squares means we can efficiently determine connectivity.

Conclusion: The flowchart leads us to use DFS/backtracking. The solution cleverly uses DFS from both source and target, exploiting the small constraint that blocked squares can only trap an area of at most len(blocked)^2 / 2 squares. If either point can explore beyond this limit, it means they've "escaped" any potential encirclement.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight comes from understanding what the blocked squares can actually do in such a massive grid. With a 1 million Γ— 1 million grid and only up to 200 blocked squares, it's impossible for the blocked squares to create a wall that spans the entire grid.

Think about it this way: if the blocked squares try to form a barrier to trap either the source or target point, what's the maximum area they can enclose? Imagine the most efficient use of blocked squares to create an enclosure - this would be forming something like a diagonal line from the edge of the grid. With n blocked squares, the maximum area that can be trapped is approximately n^2 / 2.

This leads to a clever observation: if we start from the source point and can reach more than len(blocked)^2 / 2 unique squares, we must have "escaped" any potential enclosure formed by the blocked squares. Why? Because if we were trapped, we couldn't possibly visit more squares than the maximum area that can be enclosed.

But there's another scenario to consider: what if both source and target are inside the same enclosure? They could reach each other but both would be trapped. Or what if they're in different enclosures? They couldn't reach each other even though neither is technically "stuck" in a small area.

This is why we need to check from both directions:

  1. Can the source "escape" (reach enough squares) OR reach the target directly?
  2. Can the target "escape" (reach enough squares) OR reach the source directly?

If both checks pass, it means either:

  • Both points are free (not enclosed), so they can definitely reach each other
  • Both points are in the same enclosure (since they can reach each other during the DFS)

The beauty of this approach is that we don't need to explore the entire million Γ— million grid. We only need to explore up to len(blocked)^2 / 2 squares from each starting point, making the solution feasible despite the enormous grid size.

Learn more about Depth-First Search and Breadth-First Search patterns.

Solution Approach

The implementation uses DFS (Depth-First Search) with a clever early termination condition based on the mathematical insight about the maximum area that can be trapped.

Key Data Structures:

  • s: A set containing all blocked positions as tuples for O(1) lookup
  • vis: A visited set to track explored squares during each DFS
  • dirs: Direction array (-1, 0, 1, 0, -1) used with pairwise to generate the four movement directions

DFS Function Logic:

The dfs function takes a source position, target position, and a visited set. Here's how it works:

  1. Mark Current Position: Add the current source position to the visited set as a tuple

  2. Escape Check: If len(vis) > m where m = len(blocked)^2 / 2, return True. This means we've explored enough squares to guarantee we're not trapped in an enclosure.

  3. Explore Neighbors: For each of the four directions (north, east, south, west):

    • Calculate new coordinates: x, y = source[0] + a, source[1] + b
    • Check if the new position is:
      • Within grid bounds: 0 <= x < n and 0 <= y < n where n = 10^6
      • Not blocked: (x, y) not in s
      • Not visited: (x, y) not in vis
  4. Target Check: If the new position equals the target, return True (path found)

  5. Recursive Exploration: Recursively call dfs from the new position. If it returns True, propagate the success.

  6. No Path Found: If all directions are exhausted without success, return False

Bidirectional Check:

The solution performs DFS from both directions:

return dfs(source, target, set()) and dfs(target, source, set())

This ensures that:

  • The source can either escape its potential enclosure or reach the target
  • The target can either escape its potential enclosure or reach the source

Both conditions must be true for a valid path to exist. This handles all cases:

  • Both points are free (not enclosed) β†’ both DFS calls succeed
  • Both points are in the same enclosure β†’ each can reach the other
  • Points are in different enclosures β†’ at least one DFS fails
  • One point is enclosed, the other is free β†’ the enclosed point's DFS fails

The algorithm's efficiency comes from the early termination - we never need to explore more than O(len(blocked)^2) squares, making it practical despite the grid's enormous size.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach. Imagine we have a much smaller 10Γ—10 grid (instead of 1 million Γ— 1 million) to visualize the concept.

Example Setup:

  • Grid size: 10Γ—10 (coordinates from 0 to 9)
  • Source: [1, 1]
  • Target: [8, 8]
  • Blocked squares: [[0,2], [1,2], [2,2], [2,1], [2,0]] (forming an L-shaped barrier)
  0 1 2 3 4 5 6 7 8 9
0 . . X . . . . . . .
1 . S X . . . . . . .
2 X X X . . . . . . .
3 . . . . . . . . . .
4 . . . . . . . . . .
5 . . . . . . . . . .
6 . . . . . . . . . .
7 . . . . . . . . . .
8 . . . . . . . . T .
9 . . . . . . . . . .

S = Source, T = Target, X = Blocked

Step 1: Calculate Maximum Trapped Area

  • Number of blocked squares: 5
  • Maximum area that can be trapped: 5^2 / 2 = 12.5 β†’ we'll use 12
  • If either DFS can visit more than 12 squares, that point has "escaped"

Step 2: DFS from Source [1,1]

Starting from [1,1], the DFS explores:

  1. Visit [1,1] β†’ visited set: {(1,1)}
  2. Try north [0,1] β†’ valid, visit it β†’ visited: {(1,1), (0,1)}
  3. From [0,1], try west [0,0] β†’ valid, visit β†’ visited: {(1,1), (0,1), (0,0)}
  4. Continue exploring...

The DFS would find it's blocked by the L-shaped barrier on certain sides but can escape to the east:

  • Path discovered: [1,1] β†’ [1,0] β†’ [0,0] β†’ [0,1] β†’ ... β†’ [3,1] β†’ [4,1] β†’ ...
  • After visiting 13 squares (more than our threshold of 12), the function returns True - the source has escaped!

Step 3: DFS from Target [8,8]

Starting from [8,8], the DFS explores freely as there are no nearby blocked squares:

  1. Visit [8,8] β†’ visited set: {(8,8)}
  2. Try north [7,8] β†’ valid, visit
  3. Try east [8,9] β†’ valid, visit
  4. Continue exploring...

The target quickly reaches 13+ squares since it's in open space, returning True.

Step 4: Final Result

Both DFS calls return True:

  • Source DFS: True (escaped the potential enclosure)
  • Target DFS: True (was never enclosed)

Since both are true, the function returns True - there exists a path from source to target.

Alternative Scenario - Same Enclosure:

If the blocked squares formed a complete circle around both source and target:

Blocked: forms a 5Γ—5 box around both points
Source: [3,3]
Target: [4,4]

. . . . . . .
. X X X X X .
. X . . . X .
. X . S . X .
. X . . T X .
. X X X X X .
. . . . . . .

Here:

  • Source DFS would find the target directly before hitting the escape threshold β†’ returns True
  • Target DFS would find the source directly before hitting the escape threshold β†’ returns True
  • Result: True (they can reach each other even though both are trapped)

This example demonstrates how the bidirectional check ensures we correctly identify whether a path exists, regardless of whether the points are enclosed or free.

Solution Implementation

1class Solution:
2    def isEscapePossible(
3        self, blocked: List[List[int]], source: List[int], target: List[int]
4    ) -> bool:
5        """
6        Determines if it's possible to reach target from source in a 10^6 x 10^6 grid
7        with certain cells blocked.
8      
9        The key insight: if blocked cells form an enclosure, the maximum area they can
10        enclose is approximately (len(blocked))^2 / 2 (when forming a diagonal barrier
11        against a corner). If we can explore more cells than this, we've escaped any
12        potential enclosure.
13      
14        Args:
15            blocked: List of blocked cell coordinates
16            source: Starting position [row, col]
17            target: Target position [row, col]
18          
19        Returns:
20            True if target is reachable from source, False otherwise
21        """
22      
23        def can_escape_or_reach(start_pos: List[int], end_pos: List[int], visited: set) -> bool:
24            """
25            Performs DFS to check if we can either:
26            1. Reach the end_pos from start_pos, or
27            2. Explore enough cells to prove we're not enclosed
28          
29            Args:
30                start_pos: Current starting position
31                end_pos: Target position to reach
32                visited: Set of already visited cells
33              
34            Returns:
35                True if we can reach end_pos or escape enclosure
36            """
37            # Mark current position as visited
38            visited.add(tuple(start_pos))
39          
40            # If we've explored more cells than the maximum possible enclosed area,
41            # we've definitely escaped any enclosure
42            if len(visited) > max_enclosed_area:
43                return True
44          
45            # Explore all four directions (up, right, down, left)
46            for i in range(4):
47                # Calculate next position using direction vectors
48                next_row = start_pos[0] + directions[i]
49                next_col = start_pos[1] + directions[i + 1]
50              
51                # Check if next position is valid:
52                # 1. Within grid boundaries
53                # 2. Not blocked
54                # 3. Not visited
55                if (0 <= next_row < grid_size and 
56                    0 <= next_col < grid_size and 
57                    (next_row, next_col) not in blocked_set and 
58                    (next_row, next_col) not in visited):
59                  
60                    # Check if we've reached the target
61                    if [next_row, next_col] == end_pos:
62                        return True
63                  
64                    # Continue DFS from the next position
65                    if can_escape_or_reach([next_row, next_col], end_pos, visited):
66                        return True
67          
68            return False
69      
70        # Convert blocked list to set for O(1) lookup
71        blocked_set = {(row, col) for row, col in blocked}
72      
73        # Direction vectors for moving up, right, down, left
74        # Using the pattern: (row_delta, col_delta) pairs
75        directions = [-1, 0, 1, 0, -1]  # Creates pairs: (-1,0), (0,1), (1,0), (0,-1)
76      
77        # Grid size (10^6 x 10^6)
78        grid_size = 10**6
79      
80        # Maximum area that can be enclosed by blocked cells
81        # This is approximately (number of blocked cells)^2 / 2
82        # This represents the worst-case scenario where blocked cells form a diagonal
83        max_enclosed_area = len(blocked) ** 2 // 2
84      
85        # Check both directions:
86        # 1. Can we reach target from source?
87        # 2. Can we reach source from target?
88        # Both must be true for a valid path to exist
89        return (can_escape_or_reach(source, target, set()) and 
90                can_escape_or_reach(target, source, set()))
91
1class Solution {
2    // Grid dimension (1 million x 1 million)
3    private final int GRID_SIZE = (int) 1e6;
4    // Maximum area that can be enclosed by blocked cells
5    private int maxEnclosedArea;
6    // Set to store blocked cell coordinates as encoded values
7    private Set<Long> blockedCells = new HashSet<>();
8    // Direction vectors for moving up, right, down, left
9    private final int[] directions = {-1, 0, 1, 0, -1};
10
11    /**
12     * Determines if it's possible to reach target from source in a grid with blocked cells.
13     * Uses bidirectional search to check if both source and target can escape any enclosure.
14     * 
15     * @param blocked Array of blocked cell coordinates
16     * @param source Starting position [x, y]
17     * @param target Destination position [x, y]
18     * @return true if path exists from source to target, false otherwise
19     */
20    public boolean isEscapePossible(int[][] blocked, int[] source, int[] target) {
21        // Store all blocked cells in a set for O(1) lookup
22        for (int[] blockedCell : blocked) {
23            blockedCells.add(encodeCoordinate(blockedCell[0], blockedCell[1]));
24        }
25      
26        // Calculate maximum possible enclosed area
27        // With n blocked cells, max area is approximately n*(n-1)/2
28        maxEnclosedArea = blocked.length * blocked.length / 2;
29      
30        // Extract source and target coordinates
31        int sourceX = source[0], sourceY = source[1];
32        int targetX = target[0], targetY = target[1];
33      
34        // Check both directions: source to target AND target to source
35        // Both must be reachable to ensure no enclosure blocks the path
36        return dfs(sourceX, sourceY, targetX, targetY, new HashSet<>()) && 
37               dfs(targetX, targetY, sourceX, sourceY, new HashSet<>());
38    }
39
40    /**
41     * Performs depth-first search to check if destination is reachable from start.
42     * If visited cells exceed max enclosed area, we've escaped any possible enclosure.
43     * 
44     * @param currentX Current x-coordinate
45     * @param currentY Current y-coordinate
46     * @param destinationX Target x-coordinate
47     * @param destinationY Target y-coordinate
48     * @param visitedCells Set of already visited cells in current search
49     * @return true if destination is reachable or we've escaped enclosure
50     */
51    private boolean dfs(int currentX, int currentY, int destinationX, int destinationY, 
52                       Set<Long> visitedCells) {
53        // If we've visited more cells than max enclosed area, we've escaped
54        if (visitedCells.size() > maxEnclosedArea) {
55            return true;
56        }
57      
58        // Try all four directions
59        for (int direction = 0; direction < 4; direction++) {
60            int nextX = currentX + directions[direction];
61            int nextY = currentY + directions[direction + 1];
62          
63            // Check if next position is within grid bounds
64            if (nextX >= 0 && nextX < GRID_SIZE && nextY >= 0 && nextY < GRID_SIZE) {
65                // Check if we've reached the destination
66                if (nextX == destinationX && nextY == destinationY) {
67                    return true;
68                }
69              
70                // Encode the coordinate for efficient storage and lookup
71                long encodedCoordinate = encodeCoordinate(nextX, nextY);
72              
73                // Continue DFS if cell is not blocked and not visited
74                if (!blockedCells.contains(encodedCoordinate) && 
75                    visitedCells.add(encodedCoordinate)) {
76                    if (dfs(nextX, nextY, destinationX, destinationY, visitedCells)) {
77                        return true;
78                    }
79                }
80            }
81        }
82      
83        return false;
84    }
85
86    /**
87     * Encodes 2D coordinates into a single long value for efficient storage.
88     * 
89     * @param x X-coordinate
90     * @param y Y-coordinate
91     * @return Encoded coordinate as a long value
92     */
93    private long encodeCoordinate(int x, int y) {
94        return (long) x * GRID_SIZE + y;
95    }
96}
97
1class Solution {
2public:
3    bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {
4        // Grid size is 10^6 x 10^6
5        const int GRID_SIZE = 1e6;
6      
7        // Maximum area that can be enclosed by blocked cells
8        // With n blocked cells, the maximum enclosed area is approximately n*(n-1)/2
9        int maxEnclosedArea = blocked.size() * blocked.size() / 2;
10      
11        // Type alias for long long to handle large coordinates
12        using ll = long long;
13      
14        // Set to store blocked cell positions as encoded values
15        unordered_set<ll> blockedSet;
16      
17        // Direction vectors for moving up, right, down, left
18        const int directions[5] = {-1, 0, 1, 0, -1};
19      
20        // Lambda function to encode 2D coordinates into a single number
21        auto encodePosition = [&](int row, int col) -> ll {
22            return (ll)row * GRID_SIZE + col;
23        };
24      
25        // Add all blocked cells to the set for O(1) lookup
26        for (const auto& blockedCell : blocked) {
27            blockedSet.insert(encodePosition(blockedCell[0], blockedCell[1]));
28        }
29      
30        // Extract source and target coordinates
31        int sourceRow = source[0], sourceCol = source[1];
32        int targetRow = target[0], targetCol = target[1];
33      
34        // Visited sets for both DFS searches
35        unordered_set<ll> visitedFromSource, visitedFromTarget;
36      
37        // Recursive DFS lambda function to check if we can reach the target
38        // or if we've explored enough cells to guarantee we're not trapped
39        auto dfs = [&](this auto&& dfs, int currentRow, int currentCol, 
40                      int destRow, int destCol, unordered_set<ll>& visited) -> bool {
41            // Mark current cell as visited
42            visited.insert(encodePosition(currentRow, currentCol));
43          
44            // If we've visited more cells than the maximum possible enclosed area,
45            // we must have escaped any potential enclosure
46            if (visited.size() > maxEnclosedArea) {
47                return true;
48            }
49          
50            // Try all four directions
51            for (int k = 0; k < 4; ++k) {
52                int nextRow = currentRow + directions[k];
53                int nextCol = currentCol + directions[k + 1];
54              
55                // Check if the next position is within grid bounds
56                if (nextRow >= 0 && nextRow < GRID_SIZE && 
57                    nextCol >= 0 && nextCol < GRID_SIZE) {
58                  
59                    // Check if we've reached the destination
60                    if (nextRow == destRow && nextCol == destCol) {
61                        return true;
62                    }
63                  
64                    ll encodedPosition = encodePosition(nextRow, nextCol);
65                  
66                    // Continue DFS if the cell is not blocked and not visited
67                    if (!blockedSet.contains(encodedPosition) && 
68                        !visited.contains(encodedPosition) && 
69                        dfs(nextRow, nextCol, destRow, destCol, visited)) {
70                        return true;
71                    }
72                }
73            }
74          
75            return false;
76        };
77      
78        // Check both directions: source to target AND target to source
79        // Both must be reachable to ensure a valid path exists
80        return dfs(sourceRow, sourceCol, targetRow, targetCol, visitedFromSource) && 
81               dfs(targetRow, targetCol, sourceRow, sourceCol, visitedFromTarget);
82    }
83};
84
1/**
2 * Determines if it's possible to reach the target from the source in a grid with blocked cells.
3 * Uses DFS with early termination based on the maximum area that can be enclosed by blocked cells.
4 * 
5 * @param blocked - Array of blocked cell coordinates [x, y]
6 * @param source - Starting position [x, y]
7 * @param target - Target position [x, y]
8 * @returns true if escape is possible, false otherwise
9 */
10function isEscapePossible(blocked: number[][], source: number[], target: number[]): boolean {
11    // Grid size (10^6 x 10^6)
12    const GRID_SIZE = 10 ** 6;
13  
14    // Maximum area that can be enclosed by blocked cells
15    // Formula: (blockedCount * (blockedCount - 1)) / 2
16    const maxEnclosedArea = (blocked.length ** 2) >> 1;
17  
18    // Direction vectors for moving up, right, down, left
19    const directions = [-1, 0, 1, 0, -1];
20  
21    // Set to store blocked cell positions as encoded values
22    const blockedCells = new Set<number>();
23  
24    /**
25     * Encodes 2D coordinates into a single number for efficient storage
26     * @param row - Row coordinate
27     * @param col - Column coordinate
28     * @returns Encoded position value
29     */
30    const encodePosition = (row: number, col: number): number => {
31        return row * GRID_SIZE + col;
32    };
33  
34    // Populate blocked cells set with encoded positions
35    for (const [blockedRow, blockedCol] of blocked) {
36        blockedCells.add(encodePosition(blockedRow, blockedCol));
37    }
38  
39    /**
40     * Performs depth-first search to check if target is reachable from start
41     * @param startRow - Starting row position
42     * @param startCol - Starting column position
43     * @param targetRow - Target row position
44     * @param targetCol - Target column position
45     * @param visitedCells - Set of visited cell positions
46     * @returns true if target is reachable, false otherwise
47     */
48    const depthFirstSearch = (
49        startRow: number, 
50        startCol: number, 
51        targetRow: number, 
52        targetCol: number, 
53        visitedCells: Set<number>
54    ): boolean => {
55        // Mark current position as visited
56        visitedCells.add(encodePosition(startRow, startCol));
57      
58        // If visited cells exceed max possible enclosed area, we've escaped any enclosure
59        if (visitedCells.size > maxEnclosedArea) {
60            return true;
61        }
62      
63        // Explore all four directions
64        for (let dirIndex = 0; dirIndex < 4; dirIndex++) {
65            const nextRow = startRow + directions[dirIndex];
66            const nextCol = startCol + directions[dirIndex + 1];
67          
68            // Check if next position is within grid boundaries
69            if (nextRow >= 0 && nextRow < GRID_SIZE && nextCol >= 0 && nextCol < GRID_SIZE) {
70                // Check if we've reached the target
71                if (nextRow === targetRow && nextCol === targetCol) {
72                    return true;
73                }
74              
75                const encodedNextPosition = encodePosition(nextRow, nextCol);
76              
77                // Continue DFS if cell is not blocked and not visited
78                if (!blockedCells.has(encodedNextPosition) && 
79                    !visitedCells.has(encodedNextPosition) && 
80                    depthFirstSearch(nextRow, nextCol, targetRow, targetCol, visitedCells)) {
81                    return true;
82                }
83            }
84        }
85      
86        return false;
87    };
88  
89    // Check both directions: source to target AND target to source
90    // Both must be reachable to ensure neither is trapped in an enclosure
91    return depthFirstSearch(source[0], source[1], target[0], target[1], new Set()) &&
92           depthFirstSearch(target[0], target[1], source[0], source[1], new Set());
93}
94

Time and Space Complexity

Time Complexity: O(m) where m = len(blocked)^2 / 2

The algorithm performs DFS from both source and target positions. The key insight is that the DFS is bounded by the condition len(vis) > m. Since m = len(blocked)^2 / 2, and there are at most 200 blocked cells, we have m ≀ 200^2 / 2 = 20,000.

For each DFS call:

  • The recursion explores at most m + 1 cells before returning True (when len(vis) > m)
  • Each cell is visited at most once due to the vis set
  • For each visited cell, we check 4 directions, which is O(1)
  • Checking if a position is in the blocked set s or visited set vis takes O(1) average time for hash set operations

Since we run DFS twice (once from source, once from target), the total time complexity is O(2m) = O(m).

Space Complexity: O(m) where m = len(blocked)^2 / 2

The space is used for:

  • The blocked set s: O(len(blocked)) which is at most O(200)
  • The visited set vis: O(m) as it stores at most m + 1 positions
  • The recursion call stack: O(m) in the worst case when DFS goes m levels deep

The dominant term is O(m), so the overall space complexity is O(m) where m ≀ 20,000.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Maximum Enclosed Area Calculation

Pitfall: Using the wrong formula for the maximum area that blocked cells can enclose. A common mistake is assuming blocked cells can form a square perimeter, leading to an incorrect bound like 4 * len(blocked) or len(blocked)^2.

Why it happens: The maximum enclosed area occurs when blocked cells form a diagonal barrier against a corner of the grid, creating a triangular region. This gives approximately len(blocked)^2 / 2 cells.

Solution: Use the correct formula:

max_enclosed_area = len(blocked) ** 2 // 2

2. Not Checking Both Directions

Pitfall: Only checking if source can reach target, without verifying the reverse direction:

# Incorrect: Only one direction
return can_escape_or_reach(source, target, set())

Why it fails: Consider this scenario:

  • Source is trapped in a small enclosure
  • Target is free in the open grid
  • The forward DFS from source will quickly return True after exploring the small enclosed area (exceeding the threshold)
  • But source and target are actually unreachable from each other

Solution: Always check both directions:

return (can_escape_or_reach(source, target, set()) and 
        can_escape_or_reach(target, source, set()))

3. Using List Instead of Set for Blocked Positions

Pitfall: Keeping blocked positions in a list and checking membership with in:

# Inefficient
if [next_row, next_col] in blocked:  # O(n) lookup

Why it's problematic: Each lookup takes O(n) time where n is the number of blocked cells. With potentially thousands of lookups during DFS, this creates a significant performance bottleneck.

Solution: Convert to a set of tuples for O(1) lookups:

blocked_set = {(row, col) for row, col in blocked}
# Then check:
if (next_row, next_col) not in blocked_set:  # O(1) lookup

4. Forgetting to Handle Edge Cases

Pitfall: Not considering special cases like:

  • Source and target are the same position
  • Source or target is in the blocked list
  • Empty blocked list

Solution: Add validation at the beginning:

def isEscapePossible(self, blocked, source, target):
    # Handle edge cases
    if source == target:
        return True
    if not blocked:
        return True
  
    blocked_set = {(row, col) for row, col in blocked}
    if tuple(source) in blocked_set or tuple(target) in blocked_set:
        return False
  
    # Continue with main algorithm...

5. Stack Overflow with Deep Recursion

Pitfall: Using recursive DFS on a potentially large search space can cause stack overflow, especially in Python which has a default recursion limit.

Why it happens: In worst case, DFS might need to explore up to len(blocked)^2 / 2 cells, which could be 20,000 cells for 200 blocked squares.

Solution: Either increase recursion limit or use iterative DFS:

def can_escape_or_reach_iterative(start_pos, end_pos):
    stack = [start_pos]
    visited = set()
  
    while stack:
        curr = stack.pop()
        if curr == end_pos:
            return True
          
        visited.add(tuple(curr))
        if len(visited) > max_enclosed_area:
            return True
          
        for i in range(4):
            next_row = curr[0] + directions[i]
            next_col = curr[1] + directions[i + 1]
          
            if (0 <= next_row < grid_size and 
                0 <= next_col < grid_size and 
                (next_row, next_col) not in blocked_set and 
                (next_row, next_col) not in visited):
                stack.append([next_row, next_col])
  
    return False
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More