#P2251. 第3题-小明踢足球
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 124
            Accepted: 45
            Difficulty: 7
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2024年11月13日-国内
                              
                      
          
 
- 
                        算法标签>BFS          
 
第3题-小明踢足球
题解
问题分析
在一个 m * m 的网格中,小明需要踢球,球经过障碍物和边界会停下。目标是计算小明将球踢入球门的最少踢球次数,若无法将球踢入球门则返回 -1。为了记录状态,我们需要四个变量来存储小明和球的位置。
解题思路:0-1 BFS
- 
多维度状态记录:
- 使用 
dist[x][y][fx][fy]数组记录小明和球在不同位置的最小踢球次数。这里x, y表示小明的位置,fx, fy表示球的位置。 - 初始值 
-1表示该状态未被访问。 
 - 使用 
 - 
初始化位置:
- 通过遍历网格,找到小明、球和球门的初始坐标,分别记为 
(sx, sy)、(fx, fy)、(ex, ey)。 
 - 通过遍历网格,找到小明、球和球门的初始坐标,分别记为 
 - 
0-1 BFS 队列初始化:
- 使用双端队列 
deque实现 0-1 BFS。 - 初始时,将小明和球的位置 
(sx, sy, fx, fy)入队,并将dist[sx][sy][fx][fy]初始化为 0,表示从起始状态出发,最少踢球次数为 0。 
 - 使用双端队列 
 - 
状态转移过程:
- 进行 BFS 扩展时,遍历小明的四个移动方向 
(dx, dy)。 - 小明移动:
- 对于每一个方向,计算小明移动后的新位置 
(nx, ny),跳过超出边界或遇到障碍物的位置。 - 如果小明移动后的位置与球的当前位置相同,表示要踢球。
- 计算球在踢动后的位置 
(nnx, nny),并检查新位置的有效性(不超出边界且不是障碍物)。 - 如果 
dist[nx][ny][nnx][nny] == -1,则更新dist并将新状态加入队列尾部(踢球算作一步,因此边权为 1)。 - 若球的位置达到球门,则更新 
ans为当前的踢球次数,并返回。 
 - 计算球在踢动后的位置 
 - 如果小明移动后没有踢球(
nx, ny位置与球不同),则直接将新状态入队列头部(边权为 0)。 
 - 对于每一个方向,计算小明移动后的新位置 
 
 - 进行 BFS 扩展时,遍历小明的四个移动方向 
 - 
返回结果:
- 若找到最短路径,则返回 
ans。若搜索结束未找到可行路径,则返回 -1。 
 - 若找到最短路径,则返回 
 
代码解析
from collections import deque
m = int(input())
arr = [list(map(str, input().split())) for _ in range(m)]
# 初始化小明、球和球门的位置
sx, sy, ex, ey, fx, fy = 0, 0, 0, 0, 0, 0
for i in range(m):
    for j in range(m):
        if arr[i][j] == 'B':
            fx, fy = i, j
        if arr[i][j] == 'G':
            ex, ey = i, j
        if arr[i][j] == 'X':
            sx, sy = i, j
# 移动方向
dx = [0, 0, 1, -1]
dy = [1, -1, 0, 0]
# 4维距离数组,记录状态
dist = [[[[-1] * m for _ in range(m)] for _ in range(m)] for _ in range(m)]
def bfs():
    global sx, sy, ex, ey, fx, fy
    q = deque()
    q.append((sx, sy, fx, fy))
    dist[sx][sy][fx][fy] = 0
    ans = -1
    
    while q:
        x, y, fx, fy = q.popleft()
        for i in range(4):
            nx, ny = x + dx[i], y + dy[i]
            if nx < 0 or nx >= m or ny < 0 or ny >= m or arr[nx][ny] == '1':
                continue
            # 检查是否需要踢球
            if nx == fx and ny == fy:
                nnx, nny = fx + dx[i], fy + dy[i]
                if nnx < 0 or nnx >= m or nny < 0 or nny >= m or arr[nnx][nny] == '1':
                    continue
                if dist[nx][ny][nnx][nny] == -1:
                    dist[nx][ny][nnx][nny] = dist[x][y][fx][fy] + 1
                    q.append((nx, ny, nnx, nny))
                    if nnx == ex and nny == ey:
                        if ans == -1 or ans > dist[nx][ny][nnx][nny]:
                            ans = dist[nx][ny][nnx][nny]
                            return ans
            else:
                if dist[nx][ny][fx][fy] == -1:
                    dist[nx][ny][fx][fy] = dist[x][y][fx][fy]
                    q.appendleft((nx, ny, fx, fy))
    return -1
print(bfs())
cpp
#include <iostream>
#include <deque>
#include <vector>
#include <cstring>
using namespace std;
int m; // 地图的大小
vector<vector<string>> arr; // 存储地图
int sx, sy, ex, ey, fx, fy; // 小明起始位置(sx, sy),球门位置(ex, ey),球位置(fx, fy)
int dx[] = {0, 0, 1, -1}; // 四个方向的横坐标变化量
int dy[] = {1, -1, 0, 0}; // 四个方向的纵坐标变化量
int dist[50][50][50][50]; // 记录每个状态的最短距离
// BFS函数
int bfs() {
    deque<tuple<int, int, int, int>> q; // 队列存储当前状态
    q.push_back({sx, sy, fx, fy}); // 将起始位置入队
    dist[sx][sy][fx][fy] = 0; // 初始化距离
    int ans = -1; // 最终答案,若无法到达则为-1
    // BFS循环
    while (!q.empty()) {
        auto [x, y, fx, fy] = q.front(); // 获取队首的状态
        q.pop_front(); // 队首出队
        for (int i = 0; i < 4; ++i) { // 遍历四个方向
            int nx = x + dx[i], ny = y + dy[i]; // 计算新的位置
            if (nx < 0 || nx >= m || ny < 0 || ny >= m || arr[nx][ny] == "1") continue; // 超出边界或遇到障碍物
            // 如果新位置是球的位置,则尝试踢球
            if (nx == fx && ny == fy) {
                int nnx = fx + dx[i], nny = fy + dy[i]; // 计算踢球后新的球位置
                if (nnx < 0 || nnx >= m || nny < 0 || nny >= m || arr[nnx][nny] == "1") continue; // 如果踢球位置超出边界或遇到障碍
                if (dist[nx][ny][nnx][nny] == -1) { // 如果该状态未被访问
                    dist[nx][ny][nnx][nny] = dist[x][y][fx][fy] + 1; // 更新距离
                    q.push_back({nx, ny, nnx, nny}); // 将新状态入队
                    if (nnx == ex && nny == ey) { // 如果球已经进了球门
                        if (ans == -1 || ans > dist[nx][ny][nnx][nny]) {
                            ans = dist[nx][ny][nnx][nny]; // 更新最短路径
                            return ans; // 直接返回答案
                        }
                    }
                }
            } else { // 如果没有踢球,直接移动
                if (dist[nx][ny][fx][fy] == -1) { // 如果该状态未被访问
                    dist[nx][ny][fx][fy] = dist[x][y][fx][fy]; // 保持距离不变
                    q.push_front({nx, ny, fx, fy}); // 将新的移动状态加入队列
                }
            }
        }
    }
    return -1; // 如果找不到解,返回-1
}
int main() {
    cin >> m; // 输入地图大小
    arr.resize(m, vector<string>(m)); // 初始化地图
    // 读取地图数据,并找到起始位置、小明位置、球位置和球门位置
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < m; ++j) {
            cin >> arr[i][j];
            if (arr[i][j] == "B") {
                fx = i, fy = j; // 球的位置
            }
            if (arr[i][j] == "G") {
                ex = i, ey = j; // 球门的位置
            }
            if (arr[i][j] == "X") {
                sx = i, sy = j; // 小明的位置
            }
        }
    }
    
    memset(dist, -1, sizeof(dist)); // 初始化dist数组为-1
    cout << bfs() << endl; // 执行BFS并输出结果
    return 0;
}
java
import java.util.*;
public class Main {
    static int m; // 地图的大小
    static String[][] arr; // 存储地图
    static int sx, sy, ex, ey, fx, fy; // 小明起始位置(sx, sy),球门位置(ex, ey),球位置(fx, fy)
    static int[] dx = {0, 0, 1, -1}; // 四个方向的横坐标变化量
    static int[] dy = {1, -1, 0, 0}; // 四个方向的纵坐标变化量
    static int[][][][] dist = new int[50][50][50][50]; // 记录每个状态的最短距离
    // BFS函数
    public static int bfs() {
        Deque<int[]> q = new ArrayDeque<>(); // 队列存储当前状态
        q.add(new int[]{sx, sy, fx, fy}); // 将起始位置入队
        dist[sx][sy][fx][fy] = 0; // 初始化距离
        int ans = -1; // 最终答案,若无法到达则为-1
        // BFS循环
        while (!q.isEmpty()) {
            int[] front = q.poll(); // 获取队首的状态
            int x = front[0], y = front[1], fx = front[2], fy = front[3];
            for (int i = 0; i < 4; i++) { // 遍历四个方向
                int nx = x + dx[i], ny = y + dy[i]; // 计算新的位置
                if (nx < 0 || nx >= m || ny < 0 || ny >= m || arr[nx][ny].equals("1")) continue; // 超出边界或遇到障碍物
                // 如果新位置是球的位置,则尝试踢球
                if (nx == fx && ny == fy) {
                    int nnx = fx + dx[i], nny = fy + dy[i]; // 计算踢球后新的球位置
                    if (nnx < 0 || nnx >= m || nny < 0 || nny >= m || arr[nnx][nny].equals("1")) continue; // 如果踢球位置超出边界或遇到障碍
                    if (dist[nx][ny][nnx][nny] == -1) { // 如果该状态未被访问
                        dist[nx][ny][nnx][nny] = dist[x][y][fx][fy] + 1; // 更新距离
                        q.add(new int[]{nx, ny, nnx, nny}); // 将新状态入队
                        if (nnx == ex && nny == ey) { // 如果球已经进了球门
                            if (ans == -1 || ans > dist[nx][ny][nnx][nny]) {
                                ans = dist[nx][ny][nnx][nny]; // 更新最短路径
                                return ans; // 直接返回答案
                            }
                        }
                    }
                } else { // 如果没有踢球,直接移动
                    if (dist[nx][ny][fx][fy] == -1) { // 如果该状态未被访问
                        dist[nx][ny][fx][fy] = dist[x][y][fx][fy]; // 保持距离不变
                        q.addFirst(new int[]{nx, ny, fx, fy}); // 将新的移动状态加入队列
                    }
                }
            }
        }
        return -1; // 如果找不到解,返回-1
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        m = sc.nextInt(); // 输入地图大小
        arr = new String[m][m]; // 初始化地图
        // 读取地图数据,并找到起始位置、小明位置、球位置和球门位置
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < m; j++) {
                arr[i][j] = sc.next();
                if (arr[i][j].equals("B")) {
                    fx = i; fy = j; // 球的位置
                }
                if (arr[i][j].equals("G")) {
                    ex = i; ey = j; // 球门的位置
                }
                if (arr[i][j].equals("X")) {
                    sx = i; sy = j; // 小明的位置
                }
            }
        }
        // 初始化dist数组为-1
        for (int[][][] layer1 : dist) {
            for (int[][] layer2 : layer1) {
                for (int[] layer3 : layer2) {
                    Arrays.fill(layer3, -1);
                }
            }
        }
        System.out.println(bfs()); // 执行BFS并输出结果
    }
}
总结
这段代码利用 0-1 BFS,通过多维数组跟踪每一种状态下的最少踢球次数,并在每次移动和踢球时更新队列与状态。小明的移动不增加踢球次数,因此使用双端队列进行操作,而每次踢球则计为一次操作(边权为 1)。通过这种方法,代码实现了最短路径的查找。
题目内容
小明在一个足球场上踢球。他需要绕过障碍物把球踢到门里。 足球场用大小为M∗M的正方形网格表示,其中每个元素可以是空地、障碍物、球或者是球门:
- 
小明用字母'X'表示,只要他在空地里面,就可以上下左右四个方向移动。
 - 
空地用字母'0'表示,在空地上可以自由行走。
 - 
障碍物用字母'1'表示,意味着不能通行,可能有多个障碍物。
 - 
足球用字符'B' 表示,球场上只有1个球。
 - 
球门用字符'G' 表示,球场上只有1个球门。
踢球方式:初始时小明和球可能不相邻,小明首先需要走到球的位置才能开始踢球;当移动到球边(相邻格子)后,然后继续向着球的方向移动,球会被踢到同方向相邻格的位置,即:球移动的方向只能与小明移动的方向相同,一次移动一格。
注:如果球移动前方会碰到障碍物或者边界,那么此次踢球无效,小明和球的位置均会保持不变。
举例:小明首先移动到球左侧的相邻格子,然后再向右移动到球当前所在格子(意味 着踢了一脚球),那么球就会移动到当前格的右侧一格(算作踢球一次)、且只能移动 到右侧格子。如果小明从球下方朝上移动到当前球所在格子,那么球会移动到当前格 的上方一格。
小明需要持续移动最终把球踢到门所在的格子内。返回小明把球踢到门里需要踢球的最少次数,若无法踢到门中,返回−1。
注意:足球每移动一次算做一次踢球,小明自己移动没有踢球的情况下不算踢球次 数。
 

输入描述
输入第一行输入一个正整数M(5<=M<=20)。
接下来输入1个M∗M的矩阵,一共M行,每行M个字符。
数组的行列M 取值范围5<=m<=20
数组中仅包含字符'0','1','B','G',以及'X'。
数组中'X','B'和'G'各只能出现一个。。
输出描述
返回小明把球踢到门中需要踢球的最少次数
样例1
输入
5
0 0 0 0 0
0 0 0 0 G
0 0 B 0 0
X 1 0 0 0
0 0 0 0 0
输出
3
说明
小明把球踢到门里最少需要踢3次球。
样例2
输入
5
0 0 0 0 0
0 0 0 0 G
1 0 B 0 0
X 1 0 0 0
1 0 0 0 0
输出
-1
说明
小明无法把球踢到门里