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

4 阅读4分钟

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

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

分析

  1. 象的跳跃

    • 象的跳跃是沿着对角线移动的,通过对x和y同时加相同的k来实现移动。
  2. 马的跳跃

    • 马的跳跃是一个特定的模式:可以移动到 (x + a, y + b),其中 |a| + |b| = 3 且 1 ≤ |a|, |b| ≤ 2。这意味着马的跳跃可以有 8 种可能的移动方式:

      • (x + 1, y + 2)
      • (x + 1, y - 2)
      • (x - 1, y + 2)
      • (x - 1, y - 2)
      • (x + 2, y + 1)
      • (x + 2, y - 1)
      • (x - 2, y + 1)
      • (x - 2, y - 1)
  3. 求解目标

    • 我们的任务是找到从 (x1, y1) 到 (x2, y2) 的最短路径,其中每一步可以模仿象或马的跳跃。
    • 由于每一步的移动方式是固定的,我们可以通过广度优先搜索 (BFS) 来找到最短路径。

代码实现

  1. 定义一个列表来储存马的移动方式:
horse_moves = [
    (1, 2), (1, -2), (-1, 2), (-1, -2),
    (2, 1), (2, -1), (-2, 1), (-2, -1)
]

2. 象的跳跃方式:

此函数生成象从当前位置 (x, y) 出发,所有可能的跳跃目标。我们使用了一个固定的 k 范围(-50 到 50)来生成跳跃位置,限制是为了避免无限扩展。如果没有限制,象的跳跃理论上是可以无限远的。可以根据需要调整 k 的范围。

返回值是一个列表,包含了所有可能的跳跃位置。

def generate_bishop_moves(x, y): 
    moves = [] 
    for k in range(-50, 51): 
        moves.append((x + k, y + k)) 
        moves.append((x + k, y - k)) 
    return moves

3. 广度优先搜索 (BFS) :

BFS 是一种用于图搜索的算法,适用于寻找最短路径。在这里,我们将坐标平面看作图的节点,跳跃的方式看作边。BFS 会逐层扩展搜索范围,直到找到目标位置为止。

  • 边界情况:首先,我们检查如果起点 (x1, y1) 和终点 (x2, y2) 是同一个位置,那么就不需要任何跳跃,返回步数 0

  • 初始化队列:我们使用一个双端队列 queue 来实现 BFS。队列的每个元素是一个三元组 (x, y, steps),表示当前位置 (x, y) 和已经走过的步数 steps。初始化时,起点 (x1, y1) 和步数 0 被加入队列。

  • 访问标记:我们使用一个集合 visited 来记录已经访问过的位置,避免重复搜索。

  • 主循环

    1. 出队:我们从队列中取出一个位置 (x, y, steps),表示当前正在考虑的坐标 (x, y) 和已经走的步数 steps
    2. 处理马的跳跃:对于每一个马的跳跃方式 (dx, dy),我们计算新的位置 (nx, ny),如果新位置是目标位置 (x2, y2),我们就返回当前步数 steps + 1。如果新位置没有被访问过,我们将其加入队列并标记为已访问。
    3. 处理象的跳跃:对于每一个跳跃位置 (nx, ny),我们进行相同的检查和更新。
  • BFS 的性质确保了我们找到的第一个到达终点的路径一定是最短路径,因此一旦找到目标点,我们立即返回步数。

完整代码

from collections import deque

# 马的所有可能的跳跃方式
knight_moves = [
    (1, 2), (1, -2), (-1, 2), (-1, -2),
    (2, 1), (2, -1), (-2, 1), (-2, -1)
]

# 象的跳跃方式:所有满足 x + k, y + k 或 x + k, y - k 的 (k是任意整数)
def generate_bishop_moves(x, y):
    moves = []
    for k in range(-50, 51):  # 选择一个合理的范围,这里假设跳跃不会超过50步
        moves.append((x + k, y + k))
        moves.append((x + k, y - k))
    return moves

def solution(x1, y1, x2, y2):
    # 边界情况:起点即是终点
    if (x1, y1) == (x2, y2):
        return 0
    
    # 初始化 BFS 队列
    queue = deque([(x1, y1, 0)])  # (x, y, 步数)
    visited = set()
    visited.add((x1, y1))
    
    while queue:
        x, y, steps = queue.popleft()

        # 处理马的跳跃
        for dx, dy in knight_moves:
            nx, ny = x + dx, y + dy
            if (nx, ny) == (x2, y2):
                return steps + 1
            if (nx, ny) not in visited:
                visited.add((nx, ny))
                queue.append((nx, ny, steps + 1))
        
        # 处理象的跳跃
        for nx, ny in generate_bishop_moves(x, y):
            if (nx, ny) == (x2, y2):
                return steps + 1
            if (nx, ny) not in visited:
                visited.add((nx, ny))
                queue.append((nx, ny, steps + 1))

    # 如果没有找到终点(理论上不会发生)
    return -1