Minimum Knight Moves
// https://leetcode.com/problems/minimum-knight-moves/
/*
In an infinite chess board with coordinates from -infinity to +infinity, you have a knight at square [0, 0].
A knight has 8 possible moves it can make, as illustrated below.
Each move is two squares in a cardinal direction, then one square in an orthogonal direction.
Return the minimum number of steps needed to move the knight to the square [x, y]. It is guaranteed the answer exists.
Ex1:
Input: x = 2, y = 1
Output: 1
Explanation: [0, 0] → [2, 1]
Ex2:
Input: x = 5, y = 5
Output: 4
Explanation: [0, 0] → [2, 1] → [4, 2] → [3, 4] → [5, 5]
*/
// (-1, 2), (1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1)
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
#include <cassert>
using namespace std;
struct Move {
int x;
int y;
int steps;
void print() {
cout << "x: " << x << " y: " << y << " steps: " << steps << endl;
}
Move(int x, int y, int steps) : x(x), y(y), steps(steps) {}
};
// Time: O(max(x, y) ^ 2), Space: O(max(x, y) ^ 2)
int minKnightMoves(int x, int y) {
vector<pair<int, int>> neighbors {{-1, 2}, {1, 2},
{2, 1}, {2, -1},
{1, -2}, {-1, -2},
{-2, -1}, {-2, 1}};
unordered_set<string> visited;
queue<Move> q;
q.push(Move(0, 0, 0));
visited.insert("0#0");
while (!q.empty()) {
Move curr = q.front();
q.pop();
// Found our destination
if (curr.x == x && curr.y == y) {
return curr.steps;
}
// Explore next step
for (int i = 0; i < 8; i++) {
int tempX = curr.x + neighbors[i].first;
int tempY = curr.y + neighbors[i].second;
string hash = to_string(tempX) + "#" + to_string(tempY);
if (visited.count(hash)) continue;
visited.insert(hash);
q.push(Move(tempX, tempY, curr.steps+1));
}
}
return -1;
}
string hashPosition(int x, int y) {
return to_string(x) + "#" + to_string(y);
}
// Bi-directional BFS
int minKnightMovesBidirectional(int x, int y) {
vector<pair<int, int>> offsets{
{1, 2}, {2, 1}, {2, -1}, {1, -2},
{-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}
};
queue<Move> originQueue, targetQueue;
originQueue.push(Move(0, 0, 0));
targetQueue.push(Move(x, y, 0));
unordered_map<string, int> originDistance, targetDistance;
originDistance[hashPosition(0, 0)] = 0;
targetDistance[hashPosition(x, y)] = 0;
while (true) {
Move origin = originQueue.front();
originQueue.pop();
string originXY = hashPosition(origin.x, origin.y);
if (targetDistance.count(originXY)) {
return origin.steps + targetDistance[originXY];
}
Move target = targetQueue.front();
targetQueue.pop();
string targetXY = hashPosition(target.x, target.y);
if (originDistance.count(targetXY)) {
return target.steps + originDistance[targetXY];
}
for (const auto& offset : offsets) {
// expand from the origin
int nextOriginX = origin.x + offset.first;
int nextOriginY = origin.y + offset.second;
string nextOriginXY = hashPosition(nextOriginX, nextOriginY);
if (!originDistance.count(nextOriginXY)) {
originQueue.push(Move(nextOriginX, nextOriginY, origin.steps + 1));
originDistance[nextOriginXY] = origin.steps + 1;
}
// expand from the target
int nextTargetX = target.x + offset.first;
int nextTargetY = target.y + offset.second;
string nextTargetXY = hashPosition(nextTargetX, nextTargetY);
if (!targetDistance.count(nextTargetXY)) {
targetQueue.push(Move(nextTargetX, nextTargetY, target.steps + 1));
targetDistance[nextTargetXY] = target.steps + 1;
}
}
}
return -1;
}
int main() {
assert(minKnightMoves(2, 1) == 1);
assert(minKnightMovesBidirectional(2, 1) == 1);
assert(minKnightMoves(5, 5) == 4);
assert(minKnightMovesBidirectional(5, 5) == 4);
return 0;
}```
Last updated