Before attempting this problem, you should be comfortable with:
Finding the minimum moves for a knight is a classic shortest path problem on an unweighted graph. Each cell is a node, and knight moves define the edges. BFS naturally finds the shortest path because it explores all positions at distance k before any position at distance k+1.
Starting from the origin (0, 0), we expand outward level by level. The first time we reach the target coordinates, we've found the minimum number of moves.
(0, 0) and a visited set.class Solution:
def minKnightMoves(self, x: int, y: int) -> int:
# the offsets in the eight directions
offsets = [(1, 2), (2, 1), (2, -1), (1, -2),
(-1, -2), (-2, -1), (-2, 1), (-1, 2)]
def bfs(x, y):
visited = set()
queue = deque([(0, 0)])
steps = 0
while queue:
curr_level_cnt = len(queue)
# iterate through the current level
for i in range(curr_level_cnt):
curr_x, curr_y = queue.popleft()
if (curr_x, curr_y) == (x, y):
return steps
for offset_x, offset_y in offsets:
next_x, next_y = curr_x + offset_x, curr_y + offset_y
if (next_x, next_y) not in visited:
visited.add((next_x, next_y))
queue.append((next_x, next_y))
# move on to the next level
steps += 1
return bfs(x, y)Where is the coordinate of the target.
Standard BFS explores an ever-growing circle from the origin. Bidirectional BFS reduces the search space by expanding from both the origin and the target simultaneously. When the two search frontiers meet, we've found the shortest path.
This optimization works well because the area explored grows quadratically with distance. By meeting in the middle, each search only needs to cover roughly half the distance, significantly reducing the total positions explored.
(0, 0), one from (x, y).class Solution:
def minKnightMoves(self, x: int, y: int) -> int:
# the offsets in the eight directions
offsets = [(1, 2), (2, 1), (2, -1), (1, -2),
(-1, -2), (-2, -1), (-2, 1), (-1, 2)]
# data structures needed to move from the origin point
origin_queue = deque([(0, 0, 0)])
origin_distance = {(0, 0): 0}
# data structures needed to move from the target point
target_queue = deque([(x, y, 0)])
target_distance = {(x, y): 0}
while True:
# check if we reach the circle of target
origin_x, origin_y, origin_steps = origin_queue.popleft()
if (origin_x, origin_y) in target_distance:
return origin_steps + target_distance[(origin_x, origin_y)]
# check if we reach the circle of origin
target_x, target_y, target_steps = target_queue.popleft()
if (target_x, target_y) in origin_distance:
return target_steps + origin_distance[(target_x, target_y)]
for offset_x, offset_y in offsets:
# expand the circle of origin
next_origin_x, next_origin_y = origin_x + offset_x, origin_y + offset_y
if (next_origin_x, next_origin_y) not in origin_distance:
origin_queue.append((next_origin_x, next_origin_y, origin_steps + 1))
origin_distance[(next_origin_x, next_origin_y)] = origin_steps + 1
# expand the circle of target
next_target_x, next_target_y = target_x + offset_x, target_y + offset_y
if (next_target_x, next_target_y) not in target_distance:
target_queue.append((next_target_x, next_target_y, target_steps + 1))
target_distance[(next_target_x, next_target_y)] = target_steps + 1Where is the coordinate of the target.
Due to the knight's movement symmetry, we only need to consider the first quadrant (positive x and y). The minimum moves to reach (-x, y), (x, -y), or (-x, -y) are the same as reaching (x, y).
We can define a recursive relation: the minimum moves to reach (x, y) equals 1 plus the minimum of reaching (|x-1|, |y-2|) or (|x-2|, |y-1|). The absolute values keep us in the first quadrant, and memoization prevents redundant calculations.
x and y to work in the first quadrant.(0, 0) requires 0 moves; positions where x+y = 2 require 2 moves.min(dfs(|x-1|, |y-2|), dfs(|x-2|, |y-1|)) + 1.(x, y) pair.class Solution:
def minKnightMoves(self, x: int, y: int) -> int:
@lru_cache(maxsize=None)
def dfs(x, y):
if x + y == 0:
# base case: (0, 0)
return 0
elif x + y == 2:
# base case: (1, 1), (0, 2), (2, 0)
return 2
else:
return min(dfs(abs(x - 1), abs(y - 2)), dfs(abs(x - 2), abs(y - 1))) + 1
return dfs(abs(x), abs(y))Where is the coordinate of the target.
The knight's movement is symmetric across both axes. Searching the entire coordinate plane when only the first quadrant (using absolute values of x and y) is necessary wastes time and memory. Always convert to positive coordinates first.
Without restricting the search area, BFS can explore positions far from both origin and target, leading to memory exhaustion. The optimal path rarely strays far beyond the target coordinates, so limiting exploration to a reasonable bounding box improves efficiency.
The recursive solution requires proper base cases. The position (0, 0) needs 0 moves, and positions where x + y = 2 (like (1, 1), (2, 0), (0, 2)) need exactly 2 moves. Missing these base cases causes infinite recursion or incorrect results.