LeetCode 1197 - Minimum Knight Moves
This problem asks us to compute the minimum number of moves a knight needs to travel from the origin [0, 0] to a target position [x, y] on an infinite chessboard.
Difficulty: 🟡 Medium
Topics: Breadth-First Search
Solution
Problem Understanding
This problem asks us to compute the minimum number of moves a knight needs to travel from the origin [0, 0] to a target position [x, y] on an infinite chessboard. The knight moves in an "L-shape," which means it can move two squares in one cardinal direction (up, down, left, right) and one square in the perpendicular direction. Each move is counted as a single step.
The inputs x and y represent the target coordinates. They can be negative, indicating that the target is in one of the quadrants left or below the origin. The output is a single integer representing the minimum number of knight moves required to reach [x, y].
Constraints are -300 <= x, y <= 300 and 0 <= |x| + |y| <= 300. This tells us that the target is relatively close to the origin despite the board being infinite. It also allows us to use breadth-first search without worrying about unbounded memory, as the search space is practically constrained. Important edge cases include coordinates that are very close to the origin, negative coordinates, and positions along axes, as a naive BFS could accidentally explore unnecessary quadrants. The problem guarantees that a solution exists, so we do not need to handle unreachable positions.
Approaches
A brute-force approach is to perform a BFS from [0, 0], exploring all 8 possible knight moves at each step, until reaching the target [x, y]. This approach is correct because BFS guarantees that the first time a node is reached, it is via the shortest path. However, BFS on the full infinite board could explore redundant positions in all four quadrants, making it slow.
The key insight for optimization is symmetry: because the knight's moves are symmetric, the shortest path to [x, y] is identical to the shortest path to [|x|, |y|]. Thus, we only need to search in the first quadrant (x >= 0, y >= 0). Additionally, instead of exploring the full infinite board, we can bound our BFS to a region around the target, as it is guaranteed that the knight will not need to move too far outside the bounding box of [0, 0] to [|x|, |y|].
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force BFS | O(8^( | x | + |
| Optimized BFS with Symmetry | O( | x | * |
Algorithm Walkthrough
- Convert the target coordinates to the first quadrant using absolute values:
x = abs(x)andy = abs(y). This reduces the search space due to symmetry of knight moves. - Initialize a queue for BFS starting at
(0, 0)with step count 0. Also, maintain a visited set to prevent revisiting positions. - Define the 8 possible knight moves as pairs of offsets:
(2,1),(1,2),(-1,2),(-2,1),(-2,-1),(-1,-2),(1,-2),(2,-1). - While the queue is not empty, pop the front node
(cx, cy, steps). If(cx, cy)equals(x, y), returnsteps. - For each of the 8 moves, compute the new coordinates
(nx, ny) = (cx + dx, cy + dy). - If
(nx, ny)is non-negative and not visited, add(nx, ny, steps + 1)to the queue and mark it visited. - Repeat until the target is reached.
Why it works: BFS guarantees that the first time a node is reached, it is via the minimum number of steps because each move is considered at the same "level." Using symmetry ensures we do not explore unnecessary negative coordinates. The visited set prevents cycles and repeated work.
Python Solution
from collections import deque
class Solution:
def minKnightMoves(self, x: int, y: int) -> int:
x, y = abs(x), abs(y)
directions = [(2,1),(1,2),(-1,2),(-2,1),(-2,-1),(-1,-2),(1,-2),(2,-1)]
queue = deque([(0, 0, 0)])
visited = set([(0, 0)])
while queue:
cx, cy, steps = queue.popleft()
if (cx, cy) == (x, y):
return steps
for dx, dy in directions:
nx, ny = cx + dx, cy + dy
if nx >= -1 and ny >= -1 and (nx, ny) not in visited:
visited.add((nx, ny))
queue.append((nx, ny, steps + 1))
This Python solution first maps the target to the first quadrant and then performs BFS. We initialize the queue with (0,0,0) to track coordinates and step count. The visited set ensures each position is visited at most once. The check nx >= -1 and ny >= -1 is a subtle optimization to avoid exploring positions far outside the origin that are never needed to reach the first quadrant target. Each iteration explores all 8 knight moves systematically.
Go Solution
package main
import "container/list"
func minKnightMoves(x int, y int) int {
x, y = abs(x), abs(y)
directions := [8][2]int{{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}}
visited := make(map[[2]int]bool)
queue := list.New()
queue.PushBack([3]int{0,0,0})
visited[[2]int{0,0}] = true
for queue.Len() > 0 {
elem := queue.Front()
queue.Remove(elem)
node := elem.Value.([3]int)
cx, cy, steps := node[0], node[1], node[2]
if cx == x && cy == y {
return steps
}
for _, d := range directions {
nx, ny := cx+d[0], cy+d[1]
key := [2]int{nx, ny}
if nx >= -1 && ny >= -1 && !visited[key] {
visited[key] = true
queue.PushBack([3]int{nx, ny, steps+1})
}
}
}
return 0
}
func abs(a int) int {
if a < 0 {
return -a
}
return a
}
The Go implementation mirrors the Python BFS approach. We use a list.List for the queue and a map for visited positions. Arrays are used to store coordinates and steps. The main differences include explicit use of the abs function and handling slices vs tuples.
Worked Examples
Example 1: x = 2, y = 1
| Queue | Current Node | Steps | Next Nodes |
|---|---|---|---|
| [(0,0,0)] | (0,0,0) | 0 | (2,1,1),(1,2,1),... |
| [(2,1,1),...] | (2,1,1) | 1 | Target reached, return 1 |
Example 2: x = 5, y = 5
| Queue | Current Node | Steps | Next Nodes |
|---|---|---|---|
| [(0,0,0)] | (0,0,0) | 0 | (2,1,1),(1,2,1),... |
| [(2,1,1),(1,2,1),...] | (2,1,1) | 1 | (4,2,2),(3,3,2),... |
| ... | ... | ... | ... |
| ... | (5,5,4) | 4 | Target reached, return 4 |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O( | x |
| Space | O( | x |
The time complexity is reduced compared to brute-force because we exploit symmetry and prune exploration to the first quadrant. Space complexity follows the number of unique positions visited.
Test Cases
# provided examples
assert Solution().minKnightMoves(2, 1) == 1 # simple move along x-axis
assert Solution().minKnightMoves(5, 5) == 4 # general case
# boundary values
assert Solution().minKnightMoves(0, 0) == 0 # origin, no moves needed
assert Solution().minKnightMoves(1, 0) == 3 # requires 3 moves
assert Solution().minKnightMoves(0, 1) == 3 # symmetric to above