789. Escape The Ghosts
Problem Description
You're playing a PAC-MAN style game on an infinite 2D grid. You start at position [0, 0]
and need to reach a target position [x_target, y_target]
. There are ghosts on the grid, each starting at their own positions given in the array ghosts
, where ghosts[i] = [x_i, y_i]
represents the starting position of the i-th ghost.
The game works as follows:
- Each turn, both you and all ghosts can choose to either:
- Move 1 unit in any cardinal direction (north, east, south, or west)
- Stay still
- All movements happen simultaneously
You successfully escape if you reach the target position before any ghost can reach you. If you and a ghost arrive at the same position (including the target) at the same time, you do not escape.
The problem asks you to determine if it's possible for you to escape regardless of how the ghosts move. Return true
if you can guarantee your escape, false
otherwise.
The key insight is that since ghosts can move optimally, they will always take the shortest path to intercept you. The solution uses Manhattan distance to check if any ghost can reach the target faster than or at the same time as you. If your Manhattan distance to the target (|target_x| + |target_y|)
is less than any ghost's Manhattan distance to the target (|target_x - ghost_x| + |target_y - ghost_y|)
, then you can escape. Otherwise, a ghost can always intercept you at or before the target.
Intuition
At first glance, this problem might seem complex - we need to consider all possible paths we could take and all possible moves the ghosts could make. However, let's think about what the optimal strategy for both parties would be.
Since the ghosts want to catch us, their best strategy is to move directly toward wherever we're trying to go. And since we're trying to reach the target, our best path is to move directly toward the target as well.
Here's the crucial realization: If a ghost can reach the target position in the same or fewer moves than we can, that ghost can always intercept us. Why? Because:
-
No matter what path we take to the target, we need at least
manhattan_distance
moves to get there (the Manhattan distance being|target_x| + |target_y|
from our starting position[0, 0]
). -
If a ghost can reach the target in equal or fewer moves, it can simply head straight to the target and wait for us there. Even if we try to take a detour, we'll eventually need to reach the target, and the ghost will be there.
-
The ghost doesn't need to chase us around the grid - it just needs to get to our destination before or at the same time as us.
Therefore, the problem simplifies to checking: Can we reach the target in fewer moves than any ghost can?
Our distance to target: |target_x - 0| + |target_y - 0| = |target_x| + |target_y|
Each ghost's distance to target: |target_x - ghost_x| + |target_y - ghost_y|
We can escape if and only if our distance is strictly less than every ghost's distance to the target. This is why the solution checks that all ghosts have a greater Manhattan distance to the target than we do.
Learn more about Math patterns.
Solution Approach
The implementation is elegantly simple once we understand the key insight about Manhattan distances:
-
Extract the target coordinates: We first unpack the target position into
tx
andty
for easier reference. -
Calculate our distance to target: Since we start at
[0, 0]
, our Manhattan distance to the target is simply|tx| + |ty|
, which equalsabs(tx) + abs(ty)
. -
Check each ghost's distance: For each ghost at position
[x, y]
, we calculate its Manhattan distance to the target asabs(tx - x) + abs(ty - y)
. -
Compare distances: We use Python's
all()
function to verify that every ghost has a strictly greater distance to the target than we do. The conditionabs(tx - x) + abs(ty - y) > abs(tx) + abs(ty)
must be true for all ghosts.
The solution uses a generator expression inside all()
to efficiently check this condition for each ghost:
all(abs(tx - x) + abs(ty - y) > abs(tx) + abs(ty) for x, y in ghosts)
This returns:
True
if all ghosts are farther from the target than we are (we can escape)False
if at least one ghost can reach the target as fast or faster than us (we cannot escape)
The time complexity is O(n)
where n
is the number of ghosts, as we need to check each ghost once. The space complexity is O(1)
since we only use a constant amount of extra space regardless of the input size.
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 concrete example to see how the Manhattan distance solution works.
Example 1:
- Target:
[2, 3]
- Ghosts:
[[1, 0], [4, 3]]
Step 1: Calculate our distance to the target
- We start at
[0, 0]
- Our Manhattan distance =
|2 - 0| + |3 - 0| = 2 + 3 = 5
- We need exactly 5 moves to reach the target (for example: right, right, up, up, up)
Step 2: Calculate each ghost's distance to the target
-
Ghost 1 at
[1, 0]
:- Distance =
|2 - 1| + |3 - 0| = 1 + 3 = 4
- This ghost needs only 4 moves to reach the target
- Distance =
-
Ghost 2 at
[4, 3]
:- Distance =
|2 - 4| + |3 - 3| = 2 + 0 = 2
- This ghost needs only 2 moves to reach the target
- Distance =
Step 3: Compare distances
- Our distance: 5 moves
- Ghost 1 distance: 4 moves (4 < 5, ghost is closer!)
- Ghost 2 distance: 2 moves (2 < 5, ghost is closer!)
Since both ghosts can reach the target faster than us, we cannot escape. The function returns False
.
Example 2:
- Target:
[1, 1]
- Ghosts:
[[2, 3], [-1, 2]]
Step 1: Calculate our distance
- Our Manhattan distance =
|1| + |1| = 2
Step 2: Calculate ghost distances
- Ghost 1 at
[2, 3]
: Distance =|1 - 2| + |1 - 3| = 1 + 2 = 3
- Ghost 2 at
[-1, 2]
: Distance =|1 - (-1)| + |1 - 2| = 2 + 1 = 3
Step 3: Compare
- Our distance: 2 moves
- Ghost 1: 3 moves (3 > 2, we're closer!)
- Ghost 2: 3 moves (3 > 2, we're closer!)
Since we can reach the target before any ghost, we can escape. The function returns True
.
Solution Implementation
1class Solution:
2 def escapeGhosts(self, ghosts: List[List[int]], target: List[int]) -> bool:
3 """
4 Determines if a player can reach the target before any ghost.
5
6 The player starts at origin (0, 0) and needs to reach the target.
7 Ghosts can move optimally to intercept the player.
8
9 Key insight: If any ghost can reach the target in fewer or equal steps
10 than the player, the ghost can intercept by going directly to the target.
11
12 Args:
13 ghosts: List of ghost positions [x, y]
14 target: Target position [x, y]
15
16 Returns:
17 True if player can escape to target, False otherwise
18 """
19 # Extract target coordinates
20 target_x, target_y = target
21
22 # Calculate player's Manhattan distance from origin to target
23 player_distance = abs(target_x) + abs(target_y)
24
25 # Check if all ghosts need more steps to reach target than the player
26 # For each ghost, calculate Manhattan distance to target
27 for ghost_x, ghost_y in ghosts:
28 ghost_distance = abs(target_x - ghost_x) + abs(target_y - ghost_y)
29
30 # If any ghost can reach target faster or at same time, player loses
31 if ghost_distance <= player_distance:
32 return False
33
34 # Player can escape if no ghost can intercept
35 return True
36
1class Solution {
2 /**
3 * Determines if a player can reach the target before any ghost.
4 * The player starts at origin (0, 0) and needs to reach the target position.
5 * Ghosts can intercept if they can reach the target in equal or less moves than the player.
6 *
7 * @param ghosts 2D array where each element represents a ghost's [x, y] position
8 * @param target Array representing the target position [x, y]
9 * @return true if player can escape (reach target before ghosts), false otherwise
10 */
11 public boolean escapeGhosts(int[][] ghosts, int[] target) {
12 // Extract target coordinates for clarity
13 int targetX = target[0];
14 int targetY = target[1];
15
16 // Calculate player's Manhattan distance from origin to target
17 int playerDistance = Math.abs(targetX) + Math.abs(targetY);
18
19 // Check each ghost's distance to the target
20 for (int[] ghost : ghosts) {
21 int ghostX = ghost[0];
22 int ghostY = ghost[1];
23
24 // Calculate ghost's Manhattan distance to target
25 int ghostDistance = Math.abs(targetX - ghostX) + Math.abs(targetY - ghostY);
26
27 // If any ghost can reach target faster or at same time as player, escape fails
28 if (ghostDistance <= playerDistance) {
29 return false;
30 }
31 }
32
33 // Player can reach target before all ghosts
34 return true;
35 }
36}
37
1class Solution {
2public:
3 bool escapeGhosts(vector<vector<int>>& ghosts, vector<int>& target) {
4 // Extract target coordinates for clarity
5 int targetX = target[0];
6 int targetY = target[1];
7
8 // Calculate player's Manhattan distance from origin (0, 0) to target
9 int playerDistance = abs(targetX) + abs(targetY);
10
11 // Check if any ghost can reach the target before or at the same time as the player
12 for (const auto& ghost : ghosts) {
13 int ghostX = ghost[0];
14 int ghostY = ghost[1];
15
16 // Calculate ghost's Manhattan distance to the target
17 int ghostDistance = abs(targetX - ghostX) + abs(targetY - ghostY);
18
19 // If ghost can reach target before or at the same time as player, escape fails
20 if (ghostDistance <= playerDistance) {
21 return false;
22 }
23 }
24
25 // Player can reach the target before all ghosts
26 return true;
27 }
28};
29
1/**
2 * Determines if a player can reach the target before any ghost can intercept them.
3 * Uses Manhattan distance to calculate the shortest path for both player and ghosts.
4 *
5 * @param ghosts - Array of ghost positions, where each ghost is represented as [x, y] coordinates
6 * @param target - Target position as [x, y] coordinates
7 * @returns true if player can reach target safely, false if any ghost can intercept
8 */
9function escapeGhosts(ghosts: number[][], target: number[]): boolean {
10 // Destructure target coordinates for clarity
11 const [targetX, targetY] = target;
12
13 // Calculate player's distance to target (starting from origin [0, 0])
14 const playerDistanceToTarget: number = Math.abs(targetX) + Math.abs(targetY);
15
16 // Check if any ghost can reach the target before or at the same time as the player
17 for (const ghost of ghosts) {
18 const [ghostX, ghostY] = ghost;
19
20 // Calculate Manhattan distance from current ghost to target
21 const ghostDistanceToTarget: number = Math.abs(targetX - ghostX) + Math.abs(targetY - ghostY);
22
23 // If ghost can reach target before or at same time as player, escape is impossible
24 if (ghostDistanceToTarget <= playerDistanceToTarget) {
25 return false;
26 }
27 }
28
29 // All ghosts are too far to intercept, player can escape successfully
30 return true;
31}
32
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of ghosts in the input list. The algorithm iterates through each ghost exactly once to calculate the Manhattan distance from that ghost to the target and compare it with the player's Manhattan distance to the target. Each distance calculation takes O(1)
time (simple arithmetic operations), so the overall time complexity is linear with respect to the number of ghosts.
Space Complexity: O(1)
. The algorithm only uses a constant amount of extra space regardless of the input size. The variables tx
and ty
store the target coordinates, and the generator expression in the all()
function processes one ghost at a time without storing intermediate results. No additional data structures are created that scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Overthinking Movement Strategies
Many developers initially try to simulate actual movement paths or implement complex pathfinding algorithms like BFS/DFS to track all possible moves. This leads to unnecessarily complex solutions that consider intermediate positions and collision scenarios.
Why it's wrong: The problem seems to require tracking movements, but the key insight is that both the player and ghosts will take optimal paths. Since the grid is infinite and obstacle-free, the optimal path is always the direct Manhattan path to the target.
Correct approach: Simply compare Manhattan distances. If a ghost can reach the target in equal or fewer steps, it will go directly there and wait, guaranteeing an interception.
2. Misunderstanding Simultaneous Movement
Some solutions incorrectly assume that the player moves first, then ghosts move, or vice versa. This can lead to checking if the player can "outrun" ghosts along the path.
Why it's wrong: All entities move simultaneously each turn. This means we can't gain an advantage by moving first or analyzing turn-by-turn positions.
Correct approach: Focus only on the final destination. Since movements are simultaneous, what matters is who can reach the target first, not the intermediate positions.
3. Incorrect Distance Calculation
A common error is using Euclidean distance sqrt((x2-x1)² + (y2-y1)²)
instead of Manhattan distance, or forgetting to use absolute values.
Wrong implementation:
# Incorrect - using Euclidean distance player_distance = math.sqrt(target_x**2 + target_y**2) # Incorrect - missing absolute values player_distance = target_x + target_y
Correct implementation:
player_distance = abs(target_x) + abs(target_y)
ghost_distance = abs(target_x - ghost_x) + abs(target_y - ghost_y)
4. Off-by-One Error in Comparison
Using <
instead of <=
when comparing distances, thinking the player wins ties.
Wrong:
if ghost_distance < player_distance: # Wrong - should be <= return False
Correct:
if ghost_distance <= player_distance: # Ghost wins on ties return False
The problem explicitly states: "If you and a ghost arrive at the same position at the same time, you do not escape."
Which of the following is a min heap?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!