亲子游戏问题

一、题目描述

宝宝和妈妈参加亲子游戏,在一个二维矩阵(N*N)的格子地图上,宝宝和妈妈抽签决定各自的位置,地图上每个格子有不同的糖果数量,部分格子有障碍物。

游戏规则是妈妈必须在最短的时间(每个单位时间只能走一步)到达宝宝的位置,路上的所有糖果都可以拿走,不能走障碍物的格子,只能上下左右走。

请问妈妈在最短到达宝宝位置的时间内最多拿到多少糖果(优先考虑最短时间到达的情况下尽可能多拿糖果)。

二、输入输出描述

输入描述

  • 第一行:整数N,表示二维矩阵的大小;
  • 接下来N行:每行有 N 个值,表示表格矩阵每个位置的值,

其中:

  • -3:妈妈
  • -2:宝宝
  • -1:障碍
  • ≥0:糖果数(0表示没有糖果,但是可以走)

输出描述

  • 妈妈在最短到达宝宝位置的时间内最多拿到多少糖果,行末无多余空格。

三、示例

|----|-----------------------------------------------------------|
| 输入 | 4 3 2 1 -3 1 -1 1 1 1 1 -1 2 -2 1 2 3 |
| 输出 | 9 |
| 说明 | 此地图有两条最短路径可到达宝宝位置,两条路线(左上角、右下角)都是最短路径6步,但右下角路线拿到的糖果更多,9个。 |

|----|-----------------------------------------|
| 输入 | 4 3 2 1 -3 -1 -1 1 1 1 1 -1 2 -2 1 -1 3 |
| 输出 | -1 |
| 说明 | 此地图妈妈无法到达宝宝位置 |

四、解题思路

  1. 核心思想

利用BFS 按层扩散的天然特性保证最短路径 ,同时在扩散过程中通过candy数组记录每个位置的最大糖果数(对同一位置的多个扩散来源取最大值),实现 "最短路径前提下收集最大糖果" 的目标。核心是 "分层 BFS 保证路径最短,贪心更新糖果数保证收益最大,首次到达宝宝位置即终止搜索提升效率"。

  1. 问题本质分析
  • 表层问题:在n×n网格中,从妈妈位置到宝宝位置,找最短路径并收集最多糖果;
  • 深层问题:
  1. 路径最短性问题:BFS 按层扩散的特性是,节点首次被访问时的层数即为从起点到该节点的最短路径长度,这是解决最短路径问题的最优策略(相比 DFS 无需回溯,效率更高);

  2. 糖果最大化问题:在最短路径的约束下,同一位置可能被多个同层节点(最短路径上的不同节点)扩散到,需通过贪心策略(取最大值)保留该位置能收集的最大糖果数,避免遗漏更优解;

  3. 状态标记问题:用candy数组同时承担 "访问标记"(-1表示未访问)和 "糖果数存储" 的双重职责,简化数据结构设计;

  4. 终止条件优化:首次到达宝宝位置时立即终止 BFS,因为后续层的扩散必然是更长路径,无需继续计算,提升算法效率。

  5. 核心逻辑

  • 初始化:定位妈妈位置,将其糖果数设为 0 并加入 BFS 初始队列,candy数组初始化为-1(未访问标记);
  • 分层 BFS 扩散:
  1. 用两个队列区分当前层和下一层节点,保证按层遍历,天然满足最短路径要求;
  2. 对每个当前层节点,向四个方向扩散,跳过越界坐标和墙壁;
  3. 未访问节点入队:仅当candy[newX][newY] == -1时,将其加入下一层队列,避免重复处理;
  4. 贪心更新糖果数:计算从当前节点扩散到目标位置的糖果数,与目标位置原有糖果数取最大值,保证该位置在最短路径下的最大收益;
  • 终止判断:当扩散到宝宝位置时,标记flag=true并终止 BFS,此时记录的糖果数即为最短路径下的最大糖果数;
  • 结果输出:若找到宝宝位置则输出对应糖果数,否则输出-1
  1. 步骤拆解

  2. 输入读取与初始化

    • 读取网格边长n,初始化matrix(网格原始数据)和candy(最大糖果数,初始-1)数组;
    • 遍历网格,定位妈妈位置(-3),将其candy值设为 0,并转为一维坐标加入 BFS 初始队列;
    • 初始化结果ans=-1(默认无法到达宝宝)。
  3. 分层 BFS 循环

    • 循环条件:当前队列queue非空;
    • 每轮循环初始化:newQueue(下一层节点)、flag=false(是否到达宝宝);
    • 遍历当前层所有节点:
    1. 一维坐标转二维坐标(x=pos/ny=pos%n);
    2. 遍历四个方向偏移量,计算扩散后的新坐标(newX, newY)
    3. 合法性校验:跳过越界坐标和墙壁(-1);
    4. 未访问节点入队:若candy[newX][newY] == -1,将其一维坐标加入newQueue
    5. 更新最大糖果数:计算newCandy = candy[x][y] + Math.max(0, matrix[newX][newY]),取candy[newX][newY]newCandy的最大值更新;
    6. 到达宝宝判断:若matrix[newX][newY] == -2,记录ans=candy[newX][newY],标记flag=true
  4. 终止与队列更新

    • flag=true(当前层到达宝宝),立即终止 BFS 循环;
    • 否则,将newQueue赋值给queue,继续下一层扩散。
  5. 结果输出

    • 打印ans,即最短路径下的最大糖果数(无法到达时为-1)。

五、代码实现

java 复制代码
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
  static int n;
  static int[][] matrix;
  static int[][] candy;
  static int[][] offsets = {{1, 0}, {0, -1}, {-1, 0}, {0, 1}};

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

    n = sc.nextInt();

    matrix = new int[n][n];
    candy = new int[n][n];

    LinkedList<Integer> queue = new LinkedList<>();

    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        candy[i][j] = -1;

        matrix[i][j] = sc.nextInt();
        // 妈妈的位置
        if (matrix[i][j] == -3) {
          candy[i][j] = 0;
          queue.add(i * n + j); // 二维坐标一维化
        }
      }
    }

    // 记录题解
    int ans = -1;

    // bfs 按层扩散
    while (queue.size() > 0) {
      // 记录当前扩散层的点
      LinkedList<Integer> newQueue = new LinkedList<>();

      // 当前层是否有宝宝所在的点
      boolean flag = false;

      for (int pos : queue) {
        // 源点坐标
        int x = pos / n;
        int y = pos % n;

        // 向四个方向扩散
        for (int[] offset : offsets) {
          // 当前扩散点坐标
          int newX = x + offset[0];
          int newY = y + offset[1];

          // 当前扩散点坐标越界,或者扩散点是墙,则无法扩散
          if (newX < 0 || newX >= n || newY < 0 || newY >= n || matrix[newX][newY] == -1) continue;

          // 当前扩散点坐标对应的糖果数量为-1,说明对应扩散点坐标位置还没有加入到当前扩散层
          if (candy[newX][newY] == -1) {
            newQueue.addLast(newX * n + newY); // 加入当前扩散层
          }

          // 当前扩散点可能会被多个源点扩散到,因此比较保留扩散过程中带来的较大糖果数
          // candy[newX][newY] 记录的是当前扩散点获得的糖果数
          // candy[x][y] + Math.max(0, matrix[newX][newY]) 记录的是从源点(x,y)带来的糖果数 + (newX,newY)位置原本的糖果数
          candy[newX][newY] =
              Math.max(candy[newX][newY], candy[x][y] + Math.max(0, matrix[newX][newY]));

          // 如果当前扩散点是宝宝位置,则可以停止后续层级的bfs扩散,因为已经找到宝宝的最短路径长度(即扩散层数)
          if (matrix[newX][newY] == -2) {
            ans = candy[newX][newY];
            flag = true;
          }
        }
      }

      // 已经找到去宝宝位置的最短路径和最大糖果数,则终止bfs
      if (flag) break;

      // 否则继续
      queue = newQueue;
    }

    System.out.println(ans);
  }
}
相关推荐
老鼠只爱大米1 分钟前
LeetCode经典算法面试题 #104:二叉树的最大深度(深度优先搜索、广度优先搜索等多种实现方案详细解析)
算法·leetcode·二叉树·dfs·bfs·深度优先搜索·广度优先搜索
疯狂的喵2 分钟前
分布式系统监控工具
开发语言·c++·算法
BlackWolfSky4 分钟前
鸿蒙中级课程笔记4—应用程序框架进阶2—Stage模型应用程序包结构、应用间跳转、HSP、HAR
华为·harmonyos
爱尔兰极光5 分钟前
LeetCode热题100--两数之和
算法·leetcode·职场和发展
进阶小白猿7 分钟前
Java技术八股学习Day26
java·开发语言·学习
2301_822382767 分钟前
模板编译期排序算法
开发语言·c++·算法
余瑜鱼鱼鱼9 分钟前
synchronized总结
java·开发语言
小宇的天下10 分钟前
Calibre :SVRF rule file example
java·开发语言·数据库
码农水水11 分钟前
大疆Java面试被问:使用Async-profiler进行CPU热点分析和火焰图解读
java·开发语言·jvm·数据结构·后端·面试·职场和发展
m0_5613596712 分钟前
嵌入式C++调试技术
开发语言·c++·算法