1. 项目概述:亲子游戏中的最短路径糖果收集问题
这个题目来自华为OD(Online Judge)机试系统,属于典型的图论与动态规划结合类算法题。题目描述的是在一个亲子游戏场景中,父母和孩子需要在二维矩阵中移动,从起点到终点寻找能够收集最多糖果的路径,同时要求路径长度最短。
这类问题在实际编程面试中非常常见,主要考察以下几个核心能力:
- 对图论基础算法(如BFS/DFS/Dijkstra)的掌握程度
- 动态规划思想的灵活运用
- 多条件约束下的问题建模能力
- 编程语言的熟练度(本题要求Java/Go实现)
我曾在多次技术面试中遇到类似变种题目,发现很多候选人在处理"最短路径"和"最大收益"双重条件时容易陷入思维误区。下面我将从问题分析、算法设计到代码实现,完整拆解这个问题的解决思路。
2. 问题分析与建模
2.1 题目重述与理解
假设我们有一个M×N的二维矩阵,每个格子包含:
- 障碍物(不可通过)
- 空路径(可通过,无糖果)
- 糖果点(可通过,有若干糖果)
给定起点(Sx, Sy)和终点(Ex, Ey),要求:
- 找到从起点到终点的所有可行路径
- 在这些路径中,选择糖果总和最大的
- 如果有多个最大糖果路径,选择其中最短的
2.2 输入输出规范
典型输入格式示例:
code复制5 5 // 矩阵行数 列数
0 1 0 0 0 // 矩阵内容(0=空,-1=障碍,>0=糖果数)
0 1 0 1 0
0 0 0 1 0
0 1 1 1 0
0 0 0 0 0
0 0 4 4 // 起点(0,0) 终点(4,4)
预期输出:
code复制12 5 // 最大糖果数 对应路径长度
2.3 问题复杂度分析
对于M×N的矩阵:
- 状态空间:每个位置(x,y)需要记录到达该点时已获得的糖果数和步数
- 时间复杂度:O(M×N×C) (C为最大可能糖果数)
- 空间复杂度:O(M×N×C)
注意:实际实现时需要根据具体约束调整,比如糖果总数是否有上限
3. 算法设计与实现
3.1 基础解法:BFS变种
标准BFS无法直接解决这个问题,因为需要同时考虑:
- 路径长度(BFS天然保证最短)
- 糖果数量(需要额外记录)
改进方案:
python复制from collections import deque
def max_candy(matrix, start, end):
m, n = len(matrix), len(matrix[0])
# 每个位置记录(max_candy, min_steps)
visited = [[[-1, float('inf')] for _ in range(n)] for _ in range(m)]
q = deque()
sx, sy = start
ex, ey = end
# 初始化起点
candy = matrix[sx][sy] if matrix[sx][sy] > 0 else 0
visited[sx][sy] = [candy, 0]
q.append((sx, sy, candy, 0))
dirs = [(0,1),(1,0),(0,-1),(-1,0)]
max_candy = -1
min_step = float('inf')
while q:
x, y, curr_candy, step = q.popleft()
if x == ex and y == ey:
if curr_candy > max_candy:
max_candy = curr_candy
min_step = step
elif curr_candy == max_candy:
min_step = min(min_step, step)
continue
for dx, dy in dirs:
nx, ny = x+dx, y+dy
if 0<=nx<m and 0<=ny<n and matrix[nx][ny] != -1:
new_candy = curr_candy + (matrix[nx][ny] if matrix[nx][ny]>0 else 0)
new_step = step + 1
# 只有更优解才继续处理
if (new_candy > visited[nx][ny][0]) or \
(new_candy == visited[nx][ny][0] and new_step < visited[nx][ny][1]):
visited[nx][ny] = [new_candy, new_step]
q.append((nx, ny, new_candy, new_step))
return (max_candy, min_step) if max_candy != -1 else (-1, -1)
3.2 优化解法:双向BFS
对于大规模矩阵,可以采用双向BFS优化:
- 同时从起点和终点开始搜索
- 当两边的搜索区域相遇时终止
- 需要额外处理相遇时的糖果数合并
3.3 Java实现关键代码
java复制import java.util.*;
class Solution {
static class State {
int x, y, candy, step;
State(int x, int y, int candy, int step) {
this.x = x; this.y = y;
this.candy = candy; this.step = step;
}
}
public static int[] maxCandy(int[][] matrix, int[] start, int[] end) {
int m = matrix.length, n = matrix[0].length;
int[][][] visited = new int[m][n][2]; // [0]=max_candy, [1]=min_step
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
visited[i][j][0] = -1;
visited[i][j][1] = Integer.MAX_VALUE;
}
}
Queue<State> q = new LinkedList<>();
int sx = start[0], sy = start[1];
int ex = end[0], ey = end[1];
int initCandy = matrix[sx][sy] > 0 ? matrix[sx][sy] : 0;
q.offer(new State(sx, sy, initCandy, 0));
visited[sx][sy][0] = initCandy;
visited[sx][sy][1] = 0;
int[][] dirs = {{0,1},{1,0},{0,-1},{-1,0}};
int maxCandy = -1, minStep = Integer.MAX_VALUE;
while (!q.isEmpty()) {
State curr = q.poll();
if (curr.x == ex && curr.y == ey) {
if (curr.candy > maxCandy) {
maxCandy = curr.candy;
minStep = curr.step;
} else if (curr.candy == maxCandy) {
minStep = Math.min(minStep, curr.step);
}
continue;
}
for (int[] dir : dirs) {
int nx = curr.x + dir[0], ny = curr.y + dir[1];
if (nx >= 0 && nx < m && ny >= 0 && ny < n && matrix[nx][ny] != -1) {
int newCandy = curr.candy + (matrix[nx][ny] > 0 ? matrix[nx][ny] : 0);
int newStep = curr.step + 1;
if (newCandy > visited[nx][ny][0] ||
(newCandy == visited[nx][ny][0] && newStep < visited[nx][ny][1])) {
visited[nx][ny][0] = newCandy;
visited[nx][ny][1] = newStep;
q.offer(new State(nx, ny, newCandy, newStep));
}
}
}
}
return maxCandy == -1 ? new int[]{-1, -1} : new int[]{maxCandy, minStep};
}
}
3.4 Go实现关键代码
go复制package main
import (
"container/list"
"math"
)
type State struct {
x, y int
candy int
step int
}
func maxCandy(matrix [][]int, start []int, end []int) []int {
m, n := len(matrix), len(matrix[0])
visited := make([][][]int, m)
for i := range visited {
visited[i] = make([][]int, n)
for j := range visited[i] {
visited[i][j] = []int{-1, math.MaxInt32}
}
}
q := list.New()
sx, sy := start[0], start[1]
ex, ey := end[0], end[1]
initCandy := 0
if matrix[sx][sy] > 0 {
initCandy = matrix[sx][sy]
}
q.PushBack(State{sx, sy, initCandy, 0})
visited[sx][sy][0] = initCandy
visited[sx][sy][1] = 0
dirs := [][]int{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}
maxCandy, minStep := -1, math.MaxInt32
for q.Len() > 0 {
curr := q.Remove(q.Front()).(State)
if curr.x == ex && curr.y == ey {
if curr.candy > maxCandy {
maxCandy = curr.candy
minStep = curr.step
} else if curr.candy == maxCandy && curr.step < minStep {
minStep = curr.step
}
continue
}
for _, dir := range dirs {
nx, ny := curr.x+dir[0], curr.y+dir[1]
if nx >= 0 && nx < m && ny >= 0 && ny < n && matrix[nx][ny] != -1 {
newCandy := curr.candy
if matrix[nx][ny] > 0 {
newCandy += matrix[nx][ny]
}
newStep := curr.step + 1
if newCandy > visited[nx][ny][0] ||
(newCandy == visited[nx][ny][0] && newStep < visited[nx][ny][1]) {
visited[nx][ny][0] = newCandy
visited[nx][ny][1] = newStep
q.PushBack(State{nx, ny, newCandy, newStep})
}
}
}
}
if maxCandy == -1 {
return []int{-1, -1}
}
return []int{maxCandy, minStep}
}
4. 算法优化与进阶思考
4.1 性能优化技巧
- 优先队列优化:使用优先队列(堆)按照糖果数从大到小处理,可以更快找到最大糖果路径
- 记忆化剪枝:记录到达每个位置时的最优状态,及时剪枝
- 并行搜索:对于超大矩阵,可以考虑分块并行处理
4.2 边界条件处理
实际编码时需要注意:
- 起点/终点是障碍物
- 起点/终点重合
- 矩阵全为障碍物
- 糖果总数为0的情况
- 超大矩阵的内存限制
4.3 测试用例设计
完整测试应包含:
python复制# 常规测试
normal_case = [
[0,1,0,0,0],
[0,1,0,1,0],
[0,0,0,1,0],
[0,1,1,1,0],
[0,0,0,0,0]
]
# 无解测试
no_path_case = [
[0,-1,0],
[-1,-1,0],
[0,0,0]
]
# 大糖果测试
big_candy_case = [
[10,1,1],
[1,1,1],
[1,1,10]
]
# 起点即终点
same_point_case = [
[5,0],
[0,0]
]
5. 华为OD机试注意事项
根据多次参加华为OD机试的经验,总结以下关键点:
- 输入输出格式:华为OD通常需要完全按照指定格式处理输入输出,一个空格或换行错误都可能导致失败
- 时间限制:Java/Go在华为OD系统中的执行时间限制不同,需要提前了解
- 边界检查:华为OD的测试用例往往包含极端情况,必须全面考虑
- 代码风格:虽然不强制,但良好的代码结构和注释会留下好印象
- 调试技巧:
- 提前准备常用IO模板
- 在本地构建类似的测试环境
- 对于图问题,准备可视化调试工具
提示:华为OD新系统采用双机位监考,建议提前熟悉IDE操作,不要在考试期间切换窗口或查找资料
6. 类似题目扩展
掌握本题后,可以尝试以下变种:
- 多条件路径问题:增加移动成本、时间窗口等约束
- 多代理协作:父母和孩子同时移动,协作收集糖果
- 随机地图生成:糖果和障碍物随机分布,求期望最大收益
- 三维空间扩展:将二维矩阵扩展到三维立方体
这类问题在游戏AI、物流路径规划、机器人导航等领域有广泛应用。例如在游戏开发中,NPC寻路不仅要考虑最短路径,还要考虑沿途的资源收集。
