184 lines
8.2 KiB
YAML
184 lines
8.2 KiB
YAML
title: Check if Point Is Reachable
|
|
slug: check-if-point-is-reachable
|
|
difficulty: hard
|
|
leetcode_id: 2543
|
|
leetcode_url: https://leetcode.com/problems/check-if-point-is-reachable/
|
|
categories:
|
|
- math
|
|
patterns:
|
|
- greedy
|
|
|
|
function_signature: "def is_reachable(target_x: int, target_y: int) -> bool:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { target_x: 6, target_y: 9 }
|
|
expected: false
|
|
- input: { target_x: 4, target_y: 7 }
|
|
expected: true
|
|
hidden:
|
|
- input: { target_x: 1, target_y: 1 }
|
|
expected: true
|
|
- input: { target_x: 2, target_y: 2 }
|
|
expected: true
|
|
- input: { target_x: 1, target_y: 2 }
|
|
expected: true
|
|
- input: { target_x: 3, target_y: 3 }
|
|
expected: false
|
|
- input: { target_x: 8, target_y: 16 }
|
|
expected: true
|
|
- input: { target_x: 5, target_y: 10 }
|
|
expected: false
|
|
|
|
description: |
|
|
There exists an infinitely large grid. You are currently at point `(1, 1)`, and you need to reach the point `(targetX, targetY)` using a finite number of steps.
|
|
|
|
In one **step**, you can move from point `(x, y)` to any one of the following points:
|
|
|
|
- `(x, y - x)`
|
|
- `(x - y, y)`
|
|
- `(2 * x, y)`
|
|
- `(x, 2 * y)`
|
|
|
|
Given two integers `targetX` and `targetY` representing the X-coordinate and Y-coordinate of your final position, return `true` *if you can reach the point from* `(1, 1)` *using some number of steps, and* `false` *otherwise*.
|
|
|
|
constraints: |
|
|
- `1 <= targetX, targetY <= 10^9`
|
|
|
|
examples:
|
|
- input: "targetX = 6, targetY = 9"
|
|
output: "false"
|
|
explanation: "It is impossible to reach (6, 9) from (1, 1) using any sequence of moves, so false is returned."
|
|
- input: "targetX = 4, targetY = 7"
|
|
output: "true"
|
|
explanation: "You can follow the path (1, 1) -> (1, 2) -> (1, 4) -> (1, 8) -> (1, 7) -> (2, 7) -> (4, 7)."
|
|
|
|
explanation:
|
|
intuition: |
|
|
This problem initially seems overwhelming — the grid is infinite, the target can be up to 10<sup>9</sup>, and we have four different moves. Simulating all possible paths is clearly impossible.
|
|
|
|
The breakthrough comes from **thinking backwards**. Instead of asking "can we reach `(targetX, targetY)` from `(1, 1)`?", ask "can we reach `(1, 1)` from `(targetX, targetY)` using the **reverse** operations?"
|
|
|
|
The reverse operations are:
|
|
- `(x, y)` came from `(x, x + y)` — reverse of `(x, y - x)`
|
|
- `(x, y)` came from `(x + y, y)` — reverse of `(x - y, y)`
|
|
- `(x, y)` came from `(x / 2, y)` if `x` is even — reverse of `(2 * x, y)`
|
|
- `(x, y)` came from `(x, y / 2)` if `y` is even — reverse of `(x, 2 * y)`
|
|
|
|
Now notice something remarkable: the first two reverse operations `(x, x + y)` and `(x + y, y)` are exactly the operations in the **Euclidean algorithm** for computing GCD! If we keep applying these operations, we eventually reach `(gcd(x, y), gcd(x, y))`.
|
|
|
|
The last two operations let us **divide by 2** as many times as we want. So starting from `(targetX, targetY)`:
|
|
1. We can reduce to `(gcd(targetX, targetY), gcd(targetX, targetY))` using the GCD operations
|
|
2. We can then divide by 2 repeatedly to reach `(1, 1)`
|
|
|
|
This only works if `gcd(targetX, targetY)` is a **power of 2** (including 2<sup>0</sup> = 1). If the GCD has any odd factor greater than 1, we can never eliminate it.
|
|
|
|
approach: |
|
|
We solve this using **GCD and bit manipulation**:
|
|
|
|
**Step 1: Compute the GCD**
|
|
|
|
- Calculate `g = gcd(targetX, targetY)` using the Euclidean algorithm
|
|
- This represents the value we must reduce to `(g, g)` before we can reach `(1, 1)`
|
|
|
|
|
|
|
|
**Step 2: Check if GCD is a power of 2**
|
|
|
|
- A number is a power of 2 if and only if it has exactly one bit set
|
|
- Use the classic bit trick: `g & (g - 1) == 0` returns `true` for powers of 2
|
|
- Alternatively, check that `g` has no odd factors (continuously divide by 2 until odd)
|
|
|
|
|
|
|
|
**Step 3: Return the result**
|
|
|
|
- If the GCD is a power of 2, return `true`
|
|
- Otherwise, return `false`
|
|
|
|
|
|
|
|
The elegance of this solution is that we reduced a seemingly complex pathfinding problem to a simple mathematical property check.
|
|
|
|
common_pitfalls:
|
|
- title: Attempting BFS/DFS Simulation
|
|
description: |
|
|
The instinct to simulate paths using BFS or DFS fails catastrophically here. With coordinates up to 10<sup>9</sup> and infinite branching possibilities, any simulation approach will either run out of memory or time.
|
|
|
|
The problem *looks* like a graph traversal but is actually a **number theory** problem in disguise. Recognising when to abandon standard algorithms for mathematical insights is key.
|
|
wrong_approach: "BFS/DFS to explore all reachable states"
|
|
correct_approach: "Reverse the problem and analyse GCD properties"
|
|
|
|
- title: Missing the Reverse Thinking
|
|
description: |
|
|
Working forward from `(1, 1)` is confusing because the state space explodes exponentially. Working backwards from the target is much more tractable because:
|
|
- Division by 2 shrinks coordinates
|
|
- The GCD operations have well-understood convergence properties
|
|
|
|
Always consider whether reversing the direction of a reachability problem simplifies it.
|
|
wrong_approach: "Simulate forward from (1, 1)"
|
|
correct_approach: "Work backwards from (targetX, targetY)"
|
|
|
|
- title: Incorrectly Checking Powers of 2
|
|
description: |
|
|
Common mistakes when checking if a number is a power of 2:
|
|
- Forgetting that `1` (2<sup>0</sup>) is a valid power of 2
|
|
- Using `n % 2 == 0` which only checks if `n` is even, not a power of 2
|
|
- Using floating-point logarithms which have precision issues
|
|
|
|
The bit manipulation trick `n & (n - 1) == 0` is reliable and handles all edge cases (for `n > 0`).
|
|
wrong_approach: "log2(n) is an integer check"
|
|
correct_approach: "Bit manipulation: n & (n - 1) == 0"
|
|
|
|
key_takeaways:
|
|
- "**Reverse the problem**: When forward simulation is intractable, working backwards often reveals structure"
|
|
- "**Recognise GCD operations**: The operations `(x, x + y)` and `(x + y, y)` are the Euclidean algorithm — this pattern appears in many problems"
|
|
- "**Powers of 2 and bit tricks**: `n & (n - 1) == 0` is the standard way to check if `n` is a power of 2"
|
|
- "**Number theory in disguise**: Some problems that look like graph traversal are actually about mathematical invariants"
|
|
|
|
time_complexity: "O(log(min(targetX, targetY))). Computing GCD using the Euclidean algorithm takes logarithmic time."
|
|
space_complexity: "O(1). We only use a constant number of variables."
|
|
|
|
solutions:
|
|
- approach_name: GCD Power of 2 Check
|
|
is_optimal: true
|
|
code: |
|
|
from math import gcd
|
|
|
|
def is_reachable(target_x: int, target_y: int) -> bool:
|
|
# Compute GCD of the target coordinates
|
|
g = gcd(target_x, target_y)
|
|
|
|
# Check if GCD is a power of 2
|
|
# A number is a power of 2 iff it has exactly one bit set
|
|
# n & (n - 1) clears the lowest set bit; result is 0 only for powers of 2
|
|
return g & (g - 1) == 0
|
|
explanation: |
|
|
**Time Complexity:** O(log(min(targetX, targetY))) — GCD computation dominates.
|
|
|
|
**Space Complexity:** O(1) — Only a few integer variables.
|
|
|
|
The key insight is that we can always reach `(gcd(x, y), gcd(x, y))` from `(x, y)` using the reverse GCD operations, and from there we can only reach `(1, 1)` if we can divide by 2 enough times — which requires the GCD to be a power of 2.
|
|
|
|
- approach_name: Iterative Division Check
|
|
is_optimal: false
|
|
code: |
|
|
from math import gcd
|
|
|
|
def is_reachable(target_x: int, target_y: int) -> bool:
|
|
# Compute GCD of the target coordinates
|
|
g = gcd(target_x, target_y)
|
|
|
|
# Remove all factors of 2 from GCD
|
|
while g % 2 == 0:
|
|
g //= 2
|
|
|
|
# If only factors of 2, we end up with 1
|
|
return g == 1
|
|
explanation: |
|
|
**Time Complexity:** O(log(min(targetX, targetY))) — GCD computation plus at most log(g) divisions.
|
|
|
|
**Space Complexity:** O(1) — Only integer variables used.
|
|
|
|
This approach explicitly removes all factors of 2 from the GCD. If we end up with 1, the original GCD was a power of 2. If we end up with something greater than 1, there was an odd prime factor that we cannot eliminate.
|