1. 题目背景与核心需求解析

这道来自华为OD机试的编程题目,本质上是一个典型的图论与动态规划结合的应用场景。题目描述了一位家长带着孩子在矩阵网格中移动,目标是找到一条路径,使得在限定步数内能够获取最多的糖果。这类问题在实际开发中非常常见,比如游戏中的AI寻路算法、物流配送的最优路径规划等。

1.1 问题建模要点

我们需要将题目抽象为以下几个关键要素:

  • 二维矩阵表示游戏地图,每个格子包含特定数量的糖果
  • 家长和孩子各自有独立的移动路径
  • 移动步数有限制(题目隐含条件)
  • 目标是最大化两人共同获得的糖果总数

特别需要注意的是,题目中"最短路径拿最多糖果"这个看似矛盾的要求,实际上是指:

  1. 在相同步数条件下优先选择糖果更多的路径
  2. 在糖果数量相同时选择步数更少的路径

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会消耗大量内存,我们可以通过以下方式优化:

  1. 对称性利用 :家长和孩子的位置可以交换而不影响结果
  2. 步数限制 :通常max_steps ≤ 2n-2(从左上到右下的最短路径)
  3. 滚动数组 :只保存当前步和上一步的状态

优化后的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 剪枝策略

  1. 提前终止 :当两人都到达终点时立即返回
  2. 糖果数过滤 :只保留到达同一位置时的最大糖果数状态
  3. 步数预估 :剩余步数不足以到达终点时剪枝

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 实际应用���的取舍

  1. 网格大小 :N≤10时可用基础BFS,N≤20需用优化DP
  2. 步数限制 :当K接近2N时,问题退化为完全遍历
  3. 糖果分布 :若糖果分布均匀,可采用贪心近似算法

5. 常见问题与调试技巧

5.1 典型错误排查表

错误现象 可能原因 解决方案
结果偏小 重复计算糖果 检查同一格子的访问标记
超时 状态爆炸 增加剪枝条件或改用DP
边界错误 未处理起点终点 显式初始化起点状态
内存不足 高维数组过大 改用滚动数组或BFS

5.2 调试建议

  1. 小规模测试 :先用3x3网格验证基本逻辑
  2. 可视化路径 :打印出最优路径的移动序列
  3. 中间输出 :在关键步骤打印状态矩阵
  4. 单元测试 :针对特殊糖果分布设计测试用例

关键提示:在华为OD机试中,除了正确性,还会考察代码风格和异常处理。务必添加必要的输入校验和注释。

6. 扩展思考与变种问题

6.1 题目变种

  1. 多人版 :增加更多家庭成员参与游戏
  2. 障碍物版 :网格中包含不可通过的障碍物
  3. 时间窗版 :某些糖果只在特定步数内有效

6.2 实际工程应用

  1. 物流调度 :多辆配送车的路径优化
  2. 游戏AI :NPC团队的协同移动策略
  3. 资源采集 :多机器人的任务分配与路径规划

在解决这类问题时,最重要的是准确建模状态表示和转移方程。我个人的经验是,先用小规模例子手工推导,再逐步扩展到通用解法。对于机试场景,建议优先实现正确的基础解法,再根据时间决定是否进行优化。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐