一、题目描述
宝宝和妈妈参加亲子游戏,在一个二维矩阵(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 |
| 说明 | 此地图妈妈无法到达宝宝位置 |
四、解题思路
- 核心思想
利用BFS 按层扩散的天然特性保证最短路径 ,同时在扩散过程中通过candy数组记录每个位置的最大糖果数(对同一位置的多个扩散来源取最大值),实现 "最短路径前提下收集最大糖果" 的目标。核心是 "分层 BFS 保证路径最短,贪心更新糖果数保证收益最大,首次到达宝宝位置即终止搜索提升效率"。
- 问题本质分析
- 表层问题:在
n×n网格中,从妈妈位置到宝宝位置,找最短路径并收集最多糖果; - 深层问题:
-
路径最短性问题:BFS 按层扩散的特性是,节点首次被访问时的层数即为从起点到该节点的最短路径长度,这是解决最短路径问题的最优策略(相比 DFS 无需回溯,效率更高);
-
糖果最大化问题:在最短路径的约束下,同一位置可能被多个同层节点(最短路径上的不同节点)扩散到,需通过贪心策略(取最大值)保留该位置能收集的最大糖果数,避免遗漏更优解;
-
状态标记问题:用
candy数组同时承担 "访问标记"(-1表示未访问)和 "糖果数存储" 的双重职责,简化数据结构设计; -
终止条件优化:首次到达宝宝位置时立即终止 BFS,因为后续层的扩散必然是更长路径,无需继续计算,提升算法效率。
-
核心逻辑
- 初始化:定位妈妈位置,将其糖果数设为 0 并加入 BFS 初始队列,
candy数组初始化为-1(未访问标记); - 分层 BFS 扩散:
- 用两个队列区分当前层和下一层节点,保证按层遍历,天然满足最短路径要求;
- 对每个当前层节点,向四个方向扩散,跳过越界坐标和墙壁;
- 未访问节点入队:仅当
candy[newX][newY] == -1时,将其加入下一层队列,避免重复处理; - 贪心更新糖果数:计算从当前节点扩散到目标位置的糖果数,与目标位置原有糖果数取最大值,保证该位置在最短路径下的最大收益;
- 终止判断:当扩散到宝宝位置时,标记
flag=true并终止 BFS,此时记录的糖果数即为最短路径下的最大糖果数; - 结果输出:若找到宝宝位置则输出对应糖果数,否则输出
-1。
-
步骤拆解
-
输入读取与初始化
- 读取网格边长
n,初始化matrix(网格原始数据)和candy(最大糖果数,初始-1)数组; - 遍历网格,定位妈妈位置(
-3),将其candy值设为 0,并转为一维坐标加入 BFS 初始队列; - 初始化结果
ans=-1(默认无法到达宝宝)。
- 读取网格边长
-
分层 BFS 循环
- 循环条件:当前队列
queue非空; - 每轮循环初始化:
newQueue(下一层节点)、flag=false(是否到达宝宝); - 遍历当前层所有节点:
- 一维坐标转二维坐标(
x=pos/n,y=pos%n); - 遍历四个方向偏移量,计算扩散后的新坐标
(newX, newY); - 合法性校验:跳过越界坐标和墙壁(
-1); - 未访问节点入队:若
candy[newX][newY] == -1,将其一维坐标加入newQueue; - 更新最大糖果数:计算
newCandy = candy[x][y] + Math.max(0, matrix[newX][newY]),取candy[newX][newY]与newCandy的最大值更新; - 到达宝宝判断:若
matrix[newX][newY] == -2,记录ans=candy[newX][newY],标记flag=true。
- 循环条件:当前队列
-
终止与队列更新
- 若
flag=true(当前层到达宝宝),立即终止 BFS 循环; - 否则,将
newQueue赋值给queue,继续下一层扩散。
- 若
-
结果输出
- 打印
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);
}
}