1、题目描述
2、解题思路
本题的提示中给出 |x| + |y| <= 300,因此,目的地可能位置应在下图的菱形区域内:
Java 的数组索引必须是大于等于 0,因此,为了能表示所有的坐标点,把菱形进行移动:
同时,为了避免目标(x,y)刚好在边界,我们往外再扩大成最右上角为 (666,666)
因此,原来横坐标的 [-333,+333] 变为 [0,666],纵坐标同理,因为我们计算的是移动次数,可以不用管具体的坐标。
定义一个类 Node,表示当前走到的坐标点和已走的最少步数。
从 (0,0) 出发,可以往八个方向走,但是,我们不能盲目地往八个方向都走一遍,我们肯定是要往越来越接近目标点的方向走,因此,我们还得定义一个函数用来计算两个坐标的距离。
准备工作做好了后,就可以进行 BFS 了,具体实现看代码。
3、解题代码
class Solution {
// 八个方向
int[][] dirs = new int[][]{
{1, 2}, {2, 1}, {2, -1}, {1, -2}, {-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}
};
public int minKnightMoves(int x, int y) {
int dist = 0;
int abs = getMhdDist(0, 0, x, y); // (0,0)到(x,y)的距离(横坐标之差加纵坐标之差)
// 目标最远不超过 |x| + |y| <= 300
// 666 表示把 [-333, 333] 映射为 [0, 666]
boolean[][] mark = new boolean[666][666]; // 用于标记已走过的位置
Queue<Node> queue = new LinkedList<>();
Node curNode = new Node(0, 0, 0); // 起点,已走步数为 0
Node newNode = null;
mark[333][333] = true; // 映射后的 (0,0)坐标
queue.add(curNode);
while (!queue.isEmpty()) {
curNode = queue.remove();
int curX = curNode.x;
int curY = curNode.y;
int curDist = curNode.dist; // 从 (0,0) 到 (curX,curY) 的已走步数
if (curX == x && curY == y) {
// 当前点已在终点,返回已走步数
return curDist;
}
int mhdist = getMhdDist(curX, curY, x, y); // 剩余距离
for (int[] dir : dirs) { // 往八个方向走
int newX = curX + dir[0];
int newY = curY + dir[1];
int nextDist = curDist + 1; // 新的步数等于已走步数加一
if (mark[newX + 333][newY + 333]) {
continue;
}
// 下一步走的方向一定是往目的地靠近
// 即 getMhdDist(newX, newY, x, y) < (curX, curY, x, y)
// 而不是八个方向都走一遍
if (mhdist > getMhdDist(newX, newY, x, y) || abs < 4) {
newNode = new Node(newX, newY, nextDist);
queue.add(newNode);
mark[newX + 333][newY + 333] = true;
}
}
}
return -1;
}
private int getMhdDist(int i, int j, int x, int y) {
return Math.abs(i - x) + Math.abs(j - y);
}
class Node {
int x;
int y;
public int dist; // (0,0)走到(x,y)的最少移动次数
public Node(int x, int y, int dist) {
this.x = x;
this.y = y;
this.dist = dist;
}
}
}