A clear explanation of checking whether one point can reach another by working backward with modulo.
Problem Restatement
We are given four integers:
sx, sy, tx, tyThe starting point is:
(sx, sy)The target point is:
(tx, ty)From any point (x, y), we may perform one of two moves:
(x, y) -> (x + y, y)
(x, y) -> (x, x + y)Return True if we can reach (tx, ty) from (sx, sy). Otherwise, return False.
Input and Output
| Item | Meaning |
|---|---|
| Input | Four integers sx, sy, tx, ty |
| Output | True if the target can be reached, otherwise False |
| Move 1 | Add y to x |
| Move 2 | Add x to y |
| Constraint | 1 <= sx, sy, tx, ty <= 10^9 |
Function shape:
class Solution:
def reachingPoints(
self,
sx: int,
sy: int,
tx: int,
ty: int
) -> bool:
...Examples
Example 1:
sx = 1
sy = 1
tx = 3
ty = 5One valid sequence is:
(1, 1) -> (1, 2)
(1, 2) -> (3, 2)
(3, 2) -> (3, 5)So the answer is:
TrueExample 2:
sx = 1
sy = 1
tx = 2
ty = 2This is impossible.
From (1, 1), the next points are:
(2, 1)
(1, 2)Both coordinates cannot become 2 at the same time.
So the answer is:
FalseExample 3:
sx = 1
sy = 1
tx = 1
ty = 1The start is already the target.
So the answer is:
TrueFirst Thought: Search Forward
A direct idea is to start from (sx, sy) and try both moves:
(x, y) -> (x + y, y)
(x, y) -> (x, x + y)This forms a search tree.
At each point, we branch into two new points.
We can stop when either coordinate becomes larger than the target coordinate.
This works for small numbers, but it is far too slow when coordinates may be as large as 10^9.
Problem With Forward Search
Going forward creates many possible paths.
For example, from (1, 1):
(1, 1)
(2, 1), (1, 2)
(3, 1), (2, 3), (3, 2), (1, 3)
...The number of possible states grows quickly.
But going backward is much simpler.
If we are at (tx, ty):
| Case | Previous move must have been |
|---|---|
tx > ty | The previous point was (tx - ty, ty) |
ty > tx | The previous point was (tx, ty - tx) |
tx == ty | Cannot go backward unless already at the start |
The larger coordinate must have been created by adding the smaller coordinate to it.
Key Insight
Work backward from (tx, ty) to (sx, sy).
Forward moves only increase coordinates.
So backward moves only decrease coordinates:
(tx, ty) -> (tx - ty, ty) when tx > ty
(tx, ty) -> (tx, ty - tx) when ty > txInstead of subtracting one time at a time, we can use modulo.
If tx > ty, then repeated backward steps look like:
(tx, ty)
(tx - ty, ty)
(tx - 2 * ty, ty)
(tx - 3 * ty, ty)
...So we can replace many subtractions with:
tx %= tyLikewise, if ty > tx, use:
ty %= txThis is the same idea as the Euclidean algorithm.
Algorithm
While both target coordinates are still larger than the starting coordinates:
- If
tx > ty, reducetx:
tx %= ty- Otherwise, reduce
ty:
ty %= txWe stop when at least one coordinate is no longer larger than the start coordinate.
Then we check the remaining cases.
If both coordinates match exactly:
tx == sx and ty == syreturn True.
If tx == sx, then only ty still needs to be reduced. This is possible only if the difference is a multiple of tx:
(ty - sy) % tx == 0If ty == sy, then only tx still needs to be reduced. This is possible only if the difference is a multiple of ty:
(tx - sx) % ty == 0Otherwise, return False.
Correctness
We prove that the algorithm returns True exactly when (sx, sy) can reach (tx, ty).
Forward moves only add one coordinate to the other. Therefore, both coordinates never decrease. If a target coordinate becomes smaller than the corresponding start coordinate while working backward, then reaching the target is impossible.
Now consider a target point (tx, ty).
If tx > ty, the last forward move could only have been:
(tx - ty, ty) -> (tx, ty)The other move would have changed ty, not tx. So the backward step must reduce tx by ty.
If ty > tx, the symmetric argument shows that the backward step must reduce ty by tx.
Using modulo is valid because it compresses many repeated backward subtractions of the same smaller coordinate. It does not skip any possible branch, since the backward predecessor is forced by which coordinate is larger.
The loop stops when one coordinate equals or falls below its starting coordinate. At that point, only one coordinate may still vary.
If tx == sx, then the only possible remaining backward operation is repeatedly subtracting tx from ty. Therefore, we can reach sy exactly when ty - sy is a nonnegative multiple of tx.
If ty == sy, the same reasoning applies to tx: we can reach sx exactly when tx - sx is a nonnegative multiple of ty.
If neither coordinate matches the start coordinate after the loop, no valid sequence remains.
Therefore, the algorithm is correct.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(log max(tx, ty)) | Modulo reductions behave like the Euclidean algorithm |
| Space | O(1) | Only integer variables are used |
Implementation
class Solution:
def reachingPoints(
self,
sx: int,
sy: int,
tx: int,
ty: int
) -> bool:
while tx > sx and ty > sy and tx != ty:
if tx > ty:
tx %= ty
else:
ty %= tx
if tx == sx and ty == sy:
return True
if tx == sx:
return ty > sy and (ty - sy) % tx == 0
if ty == sy:
return tx > sx and (tx - sx) % ty == 0
return FalseCode Explanation
The loop keeps reducing the target while both coordinates are still above the start:
while tx > sx and ty > sy and tx != ty:If tx is larger, it must have been produced by repeatedly adding ty, so we reduce it by modulo:
if tx > ty:
tx %= tyOtherwise, ty is larger, so we reduce ty:
else:
ty %= txAfter the loop, exact equality is immediately valid:
if tx == sx and ty == sy:
return TrueIf the x coordinate matches, the y coordinate must be reachable by adding x repeatedly:
if tx == sx:
return ty > sy and (ty - sy) % tx == 0If the y coordinate matches, the x coordinate must be reachable by adding y repeatedly:
if ty == sy:
return tx > sx and (tx - sx) % ty == 0All other cases are impossible:
return FalseTesting
def run_tests():
s = Solution()
assert s.reachingPoints(1, 1, 3, 5) is True
assert s.reachingPoints(1, 1, 2, 2) is False
assert s.reachingPoints(1, 1, 1, 1) is True
assert s.reachingPoints(1, 1, 1, 10) is True
assert s.reachingPoints(1, 1, 10, 1) is True
assert s.reachingPoints(3, 7, 3, 28) is True
assert s.reachingPoints(3, 7, 3, 29) is False
assert s.reachingPoints(9, 5, 12, 8) is False
print("all tests passed")
run_tests()Test coverage:
| Test | Why |
|---|---|
(1, 1) to (3, 5) | Standard reachable case |
(1, 1) to (2, 2) | Equal target coordinates but unreachable |
| Same start and target | Base case |
Same x coordinate | Checks one-dimensional remainder case |
Same y coordinate | Checks symmetric remainder case |
| Large remaining difference | Checks modulo shortcut |
| Non-multiple difference | Checks final divisibility condition |