国际象棋跳跃问题|豆包MarsCode AI刷题

317 阅读7分钟

国际象棋跳跃问题

问题描述

小U掌握了国际象棋中“象”和“马”的跳跃能力,在一个无限大的平面直角坐标系中,她每一步可以模仿象和马的跳跃方式移动。在每次询问中,小U需要计算从初始坐标 (x1,y1)(x1​,y1​) 到 (x2,y2)(x2​,y2​) 所需的最少步数。

  • 象的跳跃:可以移动到 (x+k,y+k)(x+k,y+k) 或 (x+k,y−k)(x+k,y−k),其中 kk 是任意整数。
  • 马的跳跃:可以移动到 (x+a,y+b)(x+a,y+b),其中 ∣a∣+∣b∣=3∣a∣+∣b∣=3 且 1≤∣a∣,∣b∣≤21≤∣a∣,∣b∣≤2。

你需要在每次询问中计算从起点到终点所需的最少步数。

测试样例

样例1:

输入:x1 = 0, y1 = 0, x2 = 1, y2 = 1 输出:1

样例2:

输入:x1 = 0, y1 = 0, x2 = 2, y2 = 1 输出:1

样例3:

输入:x1 = 0, y1 = 0, x2 = 3, y2 = 3 输出:1

样例4:

输入:x1 = -3, y1 = -2, x2 = 2, y2 = 1 输出:2

方法思路

广度优先搜索(BFS)

使用广度优先搜索(BFS)来查找从起点到终点的最短路径。

初始化: 创建一个队列和一个集合,将起始节点加入队列。集合用于记录已访问的节点。

主循环: 从队列中取出一个节点。检查该节点是否为目标节点,如果是,则返回当前步数。否则,将该节点的所有未访问过的邻居节点加入队列,并标记为已访问。

**终止条件:**如果队列为空且未找到目标节点,则返回-1(题目保证会有解,这里不会走到)。

import java.util.*;

public class Main {
    public static int solution(int x1, int y1, int x2, int y2) {
        // 马的跳跃方向
        int[][] knightMoves = {{2, 1}, {2, -1}, {-2, 1}, {-2, -1}, {1, 2}, {1, -2}, {-1, 2}, {-1, -2}};
        
        // 初始化队列和已访问集合
        Deque<int[]> queue = new ArrayDeque<>();
        Set<String> visited = new HashSet<>();
        queue.offer(new int[]{x1, y1, 0});
        visited.add(x1 + "," + y1);
        
        while (!queue.isEmpty()) {
            int[] current = queue.poll();
            int x = current[0], y = current[1], steps = current[2];
            
            // 处理马的跳跃
            for (int[] move : knightMoves) {
                int nx = x + move[0], ny = y + move[1];
                if (nx == x2 && ny == y2) return steps + 1;
                String pos = nx + "," + ny;
                if (!visited.contains(pos)) {
                    visited.add(pos);
                    queue.offer(new int[]{nx, ny, steps + 1});
                }
            }
            
            // 处理象的跳跃
            for (int k = -100; k <= 100; k++) {  // 选择一个合理的范围
                // 对角线移动
                int nx1 = x + k, ny1 = y + k;
                if (nx1 == x2 && ny1 == y2) return steps + 1;
                String pos1 = nx1 + "," + ny1;
                if (!visited.contains(pos1)) {
                    visited.add(pos1);
                    queue.offer(new int[]{nx1, ny1, steps + 1});
                }

                int nx2 = x + k, ny2 = y - k;
                if (nx2 == x2 && ny2 == y2) return steps + 1;
                String pos2 = nx2 + "," + ny2;
                if (!visited.contains(pos2)) {
                    visited.add(pos2);
                    queue.offer(new int[]{nx2, ny2, steps + 1});
                }
            }
        }
        return -1;  // 理论上不会到达这里,因为题目保证会有解
    }

    public static void main(String[] args) {
        System.out.println(solution(0, 0, 1, 1) == 1);
        System.out.println(solution(0, 0, 2, 1) == 1);
        System.out.println(solution(0, 0, 3, 3) == 1);
        System.out.println(solution(-3, -2, 2, 1) == 2);
    }
}

具体流程

以 solution(0, 0, 1, 1) 为例:

初始化: 起始位置 (0, 0),目标位置 (1, 1)。

马的跳跃方向:{{2, 1}, {2, -1}, {-2, 1}, {-2, -1}, {1, 2}, {1, -2}, {-1, 2}, {-1, -2}}。象的跳跃方向:对角线移动。将起始位置 (0, 0, 0) 加入队列,其中第三个元素表示步数。初始化已访问集合 visited,并将 (0, 0) 加入 visited。

主循环: 从队列中取出第一个节点 (0, 0, 0)。检查该节点是否为目标节点 (1, 1),结果为不是目标节点。

处理马的跳跃:计算所有可能的新位置 (nx, ny),并检查是否在 visited 中。如果不在 visited 中,将其加入队列和 visited。

处理象的跳跃:计算所有可能的新位置 (nx, ny),并检查是否在 visited 中。如果不在 visited 中,将其加入队列和 visited。

继续循环: 从队列中取出下一个节点 (2, 1, 1)。检查该节点是否为目标节点 (1, 1),不是。继续处理马的跳跃和象的跳跃。 从队列中取出节点 (1, 1, 1)。检查该节点是否为目标节点 (1, 1),是。返回步数 1。

迭代加深深度优先搜索 (IDDFS)

初始化: 从深度 0 开始,逐步增加搜索深度。每次调用 dfs 函数进行深度优先搜索,限制搜索的最大深度。

主循环: 在主函数 solution 中,使用一个无限循环,逐步增加搜索深度 depth。对于每个深度 depth,调用 dfs 函数进行深度优先搜索。如果在当前深度 depth 找到目标节点,则返回 depth 作为结果。

深度优先搜索(DFS): dfs 函数接收当前节点的坐标 (x, y)、目标节点的坐标 (x2, y2) 和当前搜索的最大深度 depth。如果 depth 为 0 且当前节点是目标节点,则返回 true。否则,递归地处理马的跳跃和象的跳跃的所有可能的新位置,并检查是否在当前深度内找到目标节点。

终止条件: 如果在某个深度 depth 内找到了目标节点,则返回该深度。由于题目保证一定有解,因此不会出现找不到解的情况。

import java.util.*;

public class Main {
    private static final int[][] knightMoves = {{2, 1}, {2, -1}, {-2, 1}, {-2, -1}, {1, 2}, {1, -2}, {-1, 2}, {-1, -2}};
    
    public static int solution(int x1, int y1, int x2, int y2) {
        for (int depth = 0; ; depth++) {
            if (dfs(x1, y1, x2, y2, depth)) {
                return depth;
            }
        }
    }

    private static boolean dfs(int x, int y, int x2, int y2, int depth) {
        if (depth == 0) {
            return x == x2 && y == y2;
        }

        // 处理马的跳跃
        for (int[] move : knightMoves) {
            int nx = x + move[0], ny = y + move[1];
            if (dfs(nx, ny, x2, y2, depth - 1)) {
                return true;
            }
        }

        // 处理象的跳跃
        for (int k = -100; k <= 100; k++) {  // 选择一个合理的范围
            int nx1 = x + k, ny1 = y + k;
            if (dfs(nx1, ny1, x2, y2, depth - 1)) {
                return true;
            }

            int nx2 = x + k, ny2 = y - k;
            if (dfs(nx2, ny2, x2, y2, depth - 1)) {
                return true;
            }
        }

        return false;
    }

    public static void main(String[] args) {
        Main solver = new Main();
        System.out.println(solver.solution(0, 0, 1, 1) == 1);
        System.out.println(solver.solution(0, 0, 2, 1) == 1);
        System.out.println(solver.solution(0, 0, 3, 3) == 1);
        System.out.println(solver.solution(-3, -2, 2, 1) == 2);
    }
}

具体流程

以 solution(0, 0, 1, 1) 为例:

初始化: 起始位置 (0, 0)。目标位置 (1, 1)。

马的跳跃方向:{{2, 1}, {2, -1}, {-2, 1}, {-2, -1}, {1, 2}, {1, -2}, {-1, 2}, {-1, -2}}。象的跳跃方向:对角线移动。

主循环: 从深度 0 开始,逐步增加搜索深度。在每次迭代中,调用 dfs 函数进行深度优先搜索。

第一次迭代(深度 0): 调用 dfs(0, 0, 1, 1, 0)。由于 depth 为 0 且当前节点不是目标节点,返回 false。

第二次迭代(深度 1): 调用 dfs(0, 0, 1, 1, 1)。

处理马的跳跃:新位置:(2, 1), (-2, 1), (2, -1), (-2, -1), (1, 2), (1, -2), (-1, 2), (-1, -2)递归调用 dfs 函数,但这些新位置都不是目标节点 (1, 1)。

处理象的跳跃:新位置:(k, k), (k, -k) (对于 -100 <= k <= 100)递归调用 dfs 函数,发现 (1, 1) 是目标节点,返回 true。

找到目标节点: 由于在深度 1 内找到了目标节点 (1, 1),返回 1。

总结 广度优先搜索 (BFS)

优点:

  1. 保证找到最短路径:BFS从起点开始逐层扩展,一旦找到目标节点,就能保证这是最短路径。
  2. 空间效率:对于有限深度的问题,BFS的空间复杂度相对较低,因为它只保留当前层和下一层的节点。
  3. 实现简单:BFS的实现相对直观,使用队列来存储待处理的节点。

缺点:

  1. 内存消耗:在无限大的棋盘上,BFS可能会消耗大量的内存,因为需要存储所有可能的中间状态。
  2. 时间效率:虽然BFS可以保证找到最短路径,但在某些情况下,它可能会探索大量的无用节点,特别是在棋盘非常大的情况下。

迭代加深度优先搜索(IDDFS)

优点:

  1. 结合DFS和BFS的优点:IDDFS逐步增加搜索深度,直到找到解,既避免了DFS的栈溢出问题,又保证了找到最短路径。
  2. 内存效率:每次搜索的深度是有限的,因此不会像BFS那样占用大量内存。
  3. 实现简单:IDDFS的实现相对简单,只需要在DFS的基础上增加一个深度限制。

缺点:

  1. 重复计算:每次搜索都需要从头开始,可能会有一些重复计算。
  2. 时间效率:虽然IDDFS可以在找到第一个解时停止,但由于每次搜索都从头开始,总体时间效率可能不如BFS高。