华为OD 2024笔试机试 - 跳马 (Java/C++/Python C卷D卷真题算法)

华为OD机试(C卷+D卷)2024真题目录(Java & c++ & python)

题目描述

马是象棋(包括中国象棋和国际象棋)中的棋子,走法是每步直一格再斜一格,即先横着或者直者走一格,然后再斜着走一个对角线,可进可退,可越过河界,俗称"马走日"字。

给定 m 行 n 列的棋盘(网格图),棋盘上只有棋子象棋中的棋子“马”,并且每个棋子有等级之分,等级为 k 的马可以跳 1~k 步(走的方式与象棋中“马”的规则一样,不可以超出棋盘位置),问是否能将所有马跳到同一位置,如果存在,输出最少需要的总步数(每匹马的步数相加),不存在则输出-1。

注:允许不同的马在跳的过程中跳到同一位置,坐标为(x,y)的马跳一次可以跳到的坐标为:(x+1, y+2),(x+1, y-2),(x+2, y+1),(x+2, y-1),(x-1, y+2),(x-1, y-2),(x-2, y+1),(x-2, y-1),的格点上,但是不可以超出棋盘范围。

输入描述

第一行输入m,n,代表 m 行 n 列的网格图棋盘(1 ≤ m, n ≤ 25)

接下来输入 m 行 n 列的网格图棋盘,如果第 i 行,第 j 列的元素为 “.” ,代表此格点没有棋子,如果为数字 k(1 ≤ k ≤ 9),代表此格点存在等级为 k 的“马”

输出描述

输出最少需要的总步数(每匹马的步数相加),不存在则输出-1。

用例1

输入

3 2
..
2.
..

输出

0

说明 只有一匹马,不需要跳动

用例2

输入

3 5
47.48
4744.
7....

输出

17

解题思路

在这里插入图片描述
因此,我们只需要遍历每一匹马,并基于BFS策略,让该马跳K步,在跳的过程中,我们记录下该马跳过的位置,马第一次跳到某位置,即为该马到达该位置的最小步数,后续该马再次跳到该位置,则非到达该位置的最小步数。
在这里插入图片描述
在这里插入图片描述
对马进行按层BFS,主要是为了记步,即走了几步,每一层都代表一步,因此当BFS进行了K层后,该马走了K步。

当马走完所有步数后,我们对 reach 和 vis 两个集合取交集(位置),将交集重新赋值给reach,这样就能保证reach记录的位置是所有马都能到达的公共位置。

如果最后reach集合的元素个数为0,则代表没有公共位置,此时返回-1。

否则,遍历reach中记录的公共位置,结合stepMap找到所有公共位置中的最小步数和。

C++/Java/Python代码如下

C++参考代码

#include <bits/stdc++.h>
using namespace std;

#define MAX_SIZE 25

// 棋盘行数, 列数
int m, n;

// 棋盘矩阵
string matrix[MAX_SIZE];

// 最小步数和矩阵,stepMatrix[i][j]记录各个马走到棋盘(i,j)位置的最小步数之和
int stepMatrix[MAX_SIZE][MAX_SIZE] = {0};

// 记录所有马都可达的公共位置坐标
set<int> reach;

// 马走日的偏移量
int offsets[8][2] = {{1, 2}, {1, -2}, {2, 1}, {2, -1}, {-1, 2}, {-1, -2}, {-2, 1}, {-2, -1}};

// 广搜
void bfs(int sx, int sy, int k) {
    // 广搜队列
    deque<vector<int>> queue;
    // (sx,sy)为马所在初始位置,马到达初始位置需要0步
    queue.push_back(vector<int>{sx, sy, 0});

    // 记录该马可以访问(sx,sy)位置
    set<int> vis;
    vis.insert(sx * n + sy); // 二维坐标一维化

    // k记录该马剩余可走步数
    while (!queue.empty() && k > 0) {
        // newQueue记录该马花费相同步数的可达的位置(即BFS按层遍历的层)
        deque<vector<int>> newQueue;

        for (const auto &item: queue) {
            int x = item[0];
            int y = item[1];
            int step = item[2];

            for (const auto &offset: offsets) {
                // 马走日到达的新位置
                int newX = x + offset[0];
                int newY = y + offset[1];

                int pos = newX * n + newY;

                // 如果新位置越界或者已访问过,则不能访问
                if (newX < 0 || newX >= m || newY < 0 || newY >= n || vis.count(pos) > 0) {
                    continue;
                }

                // 将新位置加入新层
                newQueue.push_back(vector<int>{newX, newY, step + 1});

                // 该马到达(newX, newY)位置最小步数为step+1, 由于该马首次到达(newX, newY)位置,因此step+1就是最小步数
                stepMatrix[newX][newY] += step + 1;
                // 记录该马访问过该位置,后续如果该马再次访问该位置,则不是最小步数
                vis.insert(pos);
            }
        }

        queue = newQueue;
        k--; // 剩余步数减1
    }

    // BFS完后,将公共可达位置reach和当前马可达位置取交集,交集部分就是新的公共可达位置
    set<int> intersection;
    for (const auto &pos: vis) {
        if (reach.count(pos) > 0) {
            intersection.insert(pos);
        }
    }

    reach = intersection;
}

int solution() {
    // 遍历棋盘
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            // 如果棋盘(i,j)位置是马
            if (matrix[i][j] != '.') {
                // 马的等级
                int k = matrix[i][j] - '0';
                // 对该马进行BFS走日
                bfs(i, j, k);
            }
        }
    }

    // 如果所有马走完,发现没有公共可达位置
    if (reach.empty()) {
        return -1;
    }

    // 记录所有马都可达位置的最小步数和
    int minStep = INT_MAX;

    for (const auto &pos: reach) {
        int x = pos / n;
        int y = pos % n;
        // (x,y)是所有马都可达的位置,stepMatrix[x][y]记录所有马到达此位置的步数和
        minStep = min(minStep, stepMatrix[x][y]);
    }

    return minStep;
}

int main() {
    cin >> m >> n;

    for (int i = 0; i < m; i++) {
        cin >> matrix[i];

        // 初始时假设所有位置都是各个马可达的
        for (int j = 0; j < n; j++) {
            reach.insert(i * n + j);
        }
    }

    cout << solution() << endl;

    return 0;
}

Java参考代码

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
  // 棋盘行数
  static int m;
  // 棋盘列数
  static int n;
  // 棋盘矩阵
  static char[][] map;
  // 最小步数和矩阵,stepMap[i][j]记录各个马走到棋盘(i,j)位置的最小步数之和
  static int[][] stepMap;
  // 记录所有马都可达的公共位置坐标
  static HashSet<Integer> reach;

  // 马走日的偏移量
  static int[][] offsets = {{1, 2}, {1, -2}, {2, 1}, {2, -1}, {-1, 2}, {-1, -2}, {-2, 1}, {-2, -1}};

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    m = sc.nextInt();
    n = sc.nextInt();

    map = new char[m][n];
    stepMap = new int[m][n];
    reach = new HashSet<>();

    for (int i = 0; i < m; i++) {
      map[i] = sc.next().toCharArray();

      // 初始时假设所有位置都是各个马可达的
      for (int j = 0; j < n; j++) {
        reach.add(i * n + j);
      }
    }

    System.out.println(getResult());
  }

  public static int getResult() {
    // 遍历棋盘
    for (int i = 0; i < m; i++) {
      for (int j = 0; j < n; j++) {
        // 如果棋盘(i,j)位置是马
        if (map[i][j] != '.') {
          // 马的等级
          int k = map[i][j] - '0';
          // 对该马进行BFS走日
          bfs(i, j, k);
        }
      }
    }

    // 如果所有马走完,发现没有公共可达位置
    if (reach.size() == 0) {
      return -1;
    }

    // 记录所有马都可达位置的最小步数和
    int minStep = Integer.MAX_VALUE;

    for (int pos : reach) {
      int x = pos / n;
      int y = pos % n;
      // (x,y)是所有马都可达的位置,stepMap[x][y]记录所有马到达此位置的步数和
      minStep = Math.min(minStep, stepMap[x][y]);
    }

    return minStep;
  }

  // 广搜
  public static void bfs(int sx, int sy, int k) {
    // 广搜队列
    LinkedList<int[]> queue = new LinkedList<>();
    // (sx,sy)为马所在初始位置,马到达初始位置需要0步
    queue.add(new int[]{sx, sy, 0});

    // 记录该马可以访问(sx,sy)位置
    HashSet<Integer> vis = new HashSet<>();
    vis.add(sx * n + sy); // 二维坐标一维化

    // k记录该马剩余可走步数
    while (queue.size() > 0 && k > 0) {
      // newQueue记录该马花费相同步数的可达的位置(即BFS按层遍历的层)
      LinkedList<int[]> newQueue = new LinkedList<>();

      // 按层BFS
      for (int[] tmp : queue) {
        // 当前马所在位置(x,y),以及马到达该位置的步数step
        int x = tmp[0];
        int y = tmp[1];
        int step = tmp[2];

        for (int[] offset : offsets) {
          // 马走日到达的新位置
          int newX = x + offset[0];
          int newY = y + offset[1];

          int pos = newX * n + newY;

          // 如果新位置越界或者已访问过,则不能访问
          if (newX < 0 || newX >= m || newY < 0 || newY >= n || vis.contains(pos)) continue;

          // 将新位置加入新层
          newQueue.add(new int[]{newX, newY, step + 1});
          // 该马到达(newX, newY)位置最小步数为step+1, 由于该马首次到达(newX, newY)位置,因此step+1就是最小步数
          stepMap[newX][newY] += step + 1;
          // 记录该马访问过该位置,后续如果该马再次访问该位置,则不是最小步数
          vis.add(pos);
        }
      }

      queue = newQueue;
      k--; // 剩余步数减1
    }

    // BFS完后,将公共可达位置reach和当前马可达位置取交集,交集部分就是新的公共可达位置
    reach.retainAll(vis);
  }
}

Python参考代码

import sys

# 输入获取
m, n = map(int, input().split())  # 棋盘行数, 棋盘列数
grid = [input() for _ in range(m)]  # 棋盘矩阵
stepGrid = [[0] * n for _ in range(m)]  # 最小步数和矩阵,stepMap[i][j]记录各个马走到棋盘(i,j)位置的最小步数之和

# 记录所有马都可达的公共位置坐标
reach = set()
for i in range(m):
    for j in range(n):
        reach.add(i * n + j)

# 马走日的偏移量
offsets = ((1, 2), (1, -2), (2, 1), (2, -1), (-1, 2), (-1, -2), (-2, 1), (-2, -1))

# 广搜
def bfs(sx, sy, k):
    global reach

    # 广搜队列
    # (sx,sy)为马所在初始位置,马到达初始位置需要0步
    queue = [(sx, sy, 0)]

    # 记录该马可以访问(sx,sy)位置
    vis = set()
    vis.add(sx * n + sy)  # 二维坐标一维化

    # k记录该马剩余可走步数
    while len(queue) > 0 and k > 0:
        # newQueue记录该马花费相同步数的可达的位置(即BFS按层遍历的层)
        newQueue = []

        # 按层BFS
        for x, y, step in queue:
            for offsetX, offsetY in offsets:
                # 马走日到达的新位置
                newX = x + offsetX
                newY = y + offsetY

                pos = newX * n + newY

                # 如果新位置越界或者已访问过,则不能访问
                if newX < 0 or newX >= m or newY < 0 or newY >= n or (pos in vis):
                    continue

                # 将新位置加入新层
                newQueue.append((newX, newY, step + 1))

                # 该马到达(newX, newY)位置最小步数为step+1, 由于该马首次到达(newX, newY)位置,因此step+1就是最小步数
                stepGrid[newX][newY] += step + 1

                # 记录该马访问过该位置,后续如果该马再次访问该位置,则不是最小步数
                vis.add(pos)

        queue = newQueue
        k -= 1  # 剩余步数减1

    # BFS完后,将公共可达位置reach和当前马可达位置vis取交集,交集部分就是新的公共可达位置
    reach &= vis

# 算法入口
def getResult():
    # 遍历棋盘
    for i in range(m):
        for j in range(n):
            # 如果棋盘(i,j)位置是马
            if grid[i][j] != '.':
                # 马的等级
                k = int(grid[i][j])
                # 对该马进行BFS走日
                bfs(i, j, k)

    # 如果所有马走完,发现没有公共可达位置
    if len(reach) == 0:
        return -1

    # 记录所有马都可达位置的最小步数和
    minStep = sys.maxsize

    for pos in reach

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

算法之旅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值