华为OD机试:图论与动态规划结合的糖果收集问题
·
1. 题目背景与核心需求解析
这道来自华为OD机试的编程题目,本质上是一个典型的图论与动态规划结合的应用场景。题目描述了一位家长带着孩子在矩阵网格中移动,目标是找到一条路径,使得在限定步数内能够获取最多的糖果。这类问题在实际开发中非常常见,比如游戏中的AI寻路算法、物流配送的最优路径规划等。
1.1 问题建模要点
我们需要将题目抽象为以下几个关键要素:
- 二维矩阵表示游戏地图,每个格子包含特定数量的糖果
- 家长和孩子各自有独立的移动路径
- 移动步数有限制(题目隐含条件)
- 目标是最大化两人共同获得的糖果总数
特别需要注意的是,题目中"最短路径拿最多糖果"这个看似矛盾的要求,实际上是指:
- 在相同步数条件下优先选择糖果更多的路径
- 在糖果数量相同时选择步数更少的路径
2. 算法设计与技术选型
2.1 基础解法:BFS的变种
对于这类网格路径问题,广度优先搜索(BFS)通常是首选。但标准BFS需要做以下改进:
from collections import deque
def max_candies(grid, steps):
rows, cols = len(grid), len(grid[0])
# 状态表示:(parent_x, parent_y, child_x, child_y), total_candies, steps_used
queue = deque([((0,0,0,0), grid[0][0], 0)])
visited = {}
max_candy = 0
while queue:
positions, candies, step = queue.popleft()
p_x, p_y, c_x, c_y = positions
if step > steps:
continue
if (p_x == rows-1 and p_y == cols-1 and
c_x == rows-1 and c_y == cols-1):
max_candy = max(max_candy, candies)
continue
# 生成所有可能的移动组合
for dp_x, dp_y in [(0,1),(1,0)]: # 家长移动
np_x, np_y = p_x + dp_x, p_y + dp_y
if 0 <= np_x < rows and 0 <= np_y < cols:
for dc_x, dc_y in [(0,1),(1,0)]: # 孩子移动
nc_x, nc_y = c_x + dc_x, c_y + dc_y
if 0 <= nc_x < rows and 0 <= nc_y < cols:
new_candies = candies + grid[np_x][np_y]
if (np_x, np_y) != (nc_x, nc_y):
new_candies += grid[nc_x][nc_y]
new_state = (np_x, np_y, nc_x, nc_y)
if new_state not in visited or visited[new_state] < new_candies:
visited[new_state] = new_candies
queue.append((new_state, new_candies, step+1))
return max_candy
2.2 优化方案:动态规划 + 状态压缩
当网格较大时,基础BFS会面临状态爆炸问题。我们可以采用动态规划优化:
def max_candies_dp(grid, max_steps):
n = len(grid)
# dp[k][p_i][p_j][c_i][c_j] 表示第k步时的最大糖果数
dp = [[[[[-1 for _ in range(n)] for __ in range(n)]
for ___ in range(n)] for ____ in range(n)]
for _____ in range(max_steps+1)]
dp[0][0][0][0][0] = grid[0][0]
for k in range(max_steps):
for p_i in range(n):
for p_j in range(n):
for c_i in range(n):
for c_j in range(n):
if dp[k][p_i][p_j][c_i][c_j] == -1:
continue
# 家长移动
for dp_i, dp_j in [(0,1),(1,0)]:
np_i, np_j = p_i + dp_i, p_j + dp_j
if np_i >= n or np_j >= n:
continue
# 孩子移动
for dc_i, dc_j in [(0,1),(1,0)]:
nc_i, nc_j = c_i + dc_i, c_j + dc_j
if nc_i >= n or nc_j >= n:
continue
new_candy = dp[k][p_i][p_j][c_i][c_j]
new_candy += grid[np_i][np_j]
if (np_i != nc_i) or (np_j != nc_j):
new_candy += grid[nc_i][nc_j]
if new_candy > dp[k+1][np_i][np_j][nc_i][nc_j]:
dp[k+1][np_i][np_j][nc_i][nc_j] = new_candy
return dp[max_steps][n-1][n-1][n-1][n-1]
3. 关键实现细节与优化技巧
3.1 状态表示优化
五维DP会消耗大量内存,我们可以通过以下方式优化:
- 对称性利用 :家长和孩子的位置可以交换而不影响结果
- 步数限制 :通常max_steps ≤ 2n-2(从左上到右下的最短路径)
- 滚动数组 :只保存当前步和上一步的状态
优化后的JS实现示例:
function maxCandies(grid, maxSteps) {
const n = grid.length;
let dp = new Array(n).fill().map(
() => new Array(n).fill().map(
() => new Array(n).fill().map(
() => new Array(n).fill(-1))));
dp[0][0][0][0] = grid[0][0];
let max = 0;
for (let step = 0; step < maxSteps; step++) {
let newDp = new Array(n).fill().map(
() => new Array(n).fill().map(
() => new Array(n).fill().map(
() => new Array(n).fill(-1))));
for (let p_i = 0; p_i < n; p_i++) {
for (let p_j = 0; p_j < n; p_j++) {
for (let c_i = 0; c_i < n; c_i++) {
for (let c_j = 0; c_j < n; c_j++) {
if (dp[p_i][p_j][c_i][c_j] === -1) continue;
// 家长移动方向
const directions = [[0,1],[1,0]];
for (const [dp_i, dp_j] of directions) {
const np_i = p_i + dp_i, np_j = p_j + dp_j;
if (np_i >= n || np_j >= n) continue;
// 孩子移动方向
for (const [dc_i, dc_j] of directions) {
const nc_i = c_i + dc_i, nc_j = c_j + dc_j;
if (nc_i >= n || nc_j >= n) continue;
let newVal = dp[p_i][p_j][c_i][c_j] + grid[np_i][np_j];
if (np_i !== nc_i || np_j !== nc_j) {
newVal += grid[nc_i][nc_j];
}
if (newVal > newDp[np_i][np_j][nc_i][nc_j]) {
newDp[np_i][np_j][nc_i][nc_j] = newVal;
if (np_i === n-1 && np_j === n-1 &&
nc_i === n-1 && nc_j === n-1) {
max = Math.max(max, newVal);
}
}
}
}
}
}
}
}
dp = newDp;
}
return max;
}
3.2 剪枝策略
- 提前终止 :当两人都到达终点时立即返回
- 糖果数过滤 :只保留到达同一位置时的最大糖果数状态
- 步数预估 :剩余步数不足以到达终点时剪枝
4. 复杂度分析与适用场景
4.1 时间复杂度对比
| 算法类型 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 基础BFS | O((MN)^2 * K) | O((MN)^2) | 小网格(K<10) |
| 五维DP | O(K*(MN)^2) | O((MN)^2) | 中等网格 |
| 优化DP | O(K*N^4) | O(N^4) | 规则网格 |
4.2 实际应用���的取舍
- 网格大小 :N≤10时可用基础BFS,N≤20需用优化DP
- 步数限制 :当K接近2N时,问题退化为完全遍历
- 糖果分布 :若糖果分布均匀,可采用贪心近似算法
5. 常见问题与调试技巧
5.1 典型错误排查表
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 结果偏小 | 重复计算糖果 | 检查同一格子的访问标记 |
| 超时 | 状态爆炸 | 增加剪枝条件或改用DP |
| 边界错误 | 未处理起点终点 | 显式初始化起点状态 |
| 内存不足 | 高维数组过大 | 改用滚动数组或BFS |
5.2 调试建议
- 小规模测试 :先用3x3网格验证基本逻辑
- 可视化路径 :打印出最优路径的移动序列
- 中间输出 :在关键步骤打印状态矩阵
- 单元测试 :针对特殊糖果分布设计测试用例
关键提示:在华为OD机试中,除了正确性,还会考察代码风格和异常处理。务必添加必要的输入校验和注释。
6. 扩展思考与变种问题
6.1 题目变种
- 多人版 :增加更多家庭成员参与游戏
- 障碍物版 :网格中包含不可通过的障碍物
- 时间窗版 :某些糖果只在特定步数内有效
6.2 实际工程应用
- 物流调度 :多辆配送车的路径优化
- 游戏AI :NPC团队的协同移动策略
- 资源采集 :多机器人的任务分配与路径规划
在解决这类问题时,最重要的是准确建模状态表示和转移方程。我个人的经验是,先用小规模例子手工推导,再逐步扩展到通用解法。对于机试场景,建议优先实现正确的基础解法,再根据时间决定是否进行优化。
更多推荐


所有评论(0)