LeetCode 3418.机器人可以获得的最大金币数

LeetCode 3418.机器人可以获得的最大金币数

题目描述

给你一个 m x n 的网格 coins。一个机器人从网格的左上角 (0, 0) 出发,目标是到达网格的右下角 (m - 1, n - 1)。在任意时刻,机器人只能向右或向下移动。

网格中的每个单元格包含一个值 coins[i][j]

  • 如果 coins[i][j] >= 0,机器人可以获得该单元格的金币。
  • 如果 coins[i][j] < 0,机器人会遇到一个强盗,强盗会抢走该单元格数值的 绝对值 的金币。

机器人有一项特殊能力,可以在行程中 最多感化 2 个单元格的强盗,从而防止这些单元格的金币被抢走(即该格子的负数视为 0)。

注意:机器人的总金币数可以是负数。

返回机器人在路径上可以获得的 最大金币数

解题思路

这是一道典型的网格路径 DP + 状态拓展 问题。如果没有任何豁免能力,我们只能简单地计算从左上到右下的最大路径和(允许负数)。但现在多了一个限制:最多可以将 2 个负值单元格变为 0

我们采用 自顶向下的记忆化搜索(递归 + 备忘录),这样写起来比迭代 DP 更直观,也方便处理边界和特殊状态。

状态定义

定义递归函数 dfs(i, j, k) 表示:

从起点 (0, 0) 走到 (i, j),且剩余 k 次豁免机会(即还能将 k 个负数单元格变成 0)时,所能获得的最大路径和。

  • 0 <= i < m0 <= j < n
  • k = 0, 1, 2(因为最多感化 2 个强盗)

最终答案就是 dfs(m-1, n-1, 2)

状态转移

对于当前格子 (i, j),其值为 x = coins[i][j]

  1. 不豁免当前格子

    无论 x 是正是负,直接加上它的值。此时豁免次数不变(k 不变)。

    上一步要么来自上方 (i-1, j),要么来自左方 (i, j-1),因此:

    复制代码
    res1 = max(dfs(i-1, j, k), dfs(i, j-1, k)) + x
  2. 豁免当前格子(仅当 k > 0x < 0

    如果还有豁免机会,并且当前格子是负数,我们可以选择"感化"这个强盗,即该格子对路径和的贡献为 0(而不是 x),但同时消耗一次豁免机会(k-1)。

    上一步来自上方或左方,且上一步使用的豁免次数为 k-1

    复制代码
    res2 = max(dfs(i-1, j, k-1), dfs(i, j-1, k-1))

    注意这里没有 + x,相当于加 0。

最终的 dfs(i, j, k)max(res1, res2)(如果 res2 合法的话)。

边界条件

  • 越界 :当 i < 0j < 0 时,路径不存在,返回 INT_MIN 表示负无穷(不可达)。
  • 起点 (0, 0):没有前驱格子,需要特殊处理:
    • 如果还有豁免机会(k > 0),我们可以选择豁免它(若为负数),即 max(x, 0)
    • 否则(k == 0),只能取原值 x

记忆化

使用一个三维数组 memo[i][j][k] 记录已经计算过的状态,初始化为 INT_MIN

当某个状态被第一次计算后,将其结果存入 memo,后续直接返回,避免重复递归。

代码实现(C++)

cpp 复制代码
class Solution {
public:
    int maximumAmount(vector<vector<int>>& coins) {
        int m = coins.size(), n = coins[0].size();
        // memo[i][j][k] : 走到 (i,j) 剩余 k 次豁免的最大和
        vector memo(m, vector(n, array<int, 3>{INT_MIN, INT_MIN, INT_MIN}));
        
        auto dfs = [&](this auto&& dfs, int i, int j, int k) -> int {
            if (i < 0 || j < 0) return INT_MIN; // 越界不可达
            int x = coins[i][j];
            // 起点
            if (i == 0 && j == 0) {
                return memo[i][j][k] = (k ? max(x, 0) : x);
            }
            int& res = memo[i][j][k];
            if (res != INT_MIN) return res; // 已计算过
            
            // 不豁免当前格子
            res = max(dfs(i - 1, j, k), dfs(i, j - 1, k)) + x;
            // 如果还能豁免且当前格子为负数,尝试豁免
            if (k && x < 0) {
                res = max({res, dfs(i - 1, j, k - 1), dfs(i, j - 1, k - 1)});
            }
            return res;
        };
        
        return dfs(m - 1, n - 1, 2);
    }
};

注意 :代码使用了 C++23 的 this auto&& dfs 语法(显式对象参数),使得 lambda 可以递归调用自身,无需借助 std::function,性能更好。如果编译器不支持 C++23,可以用 std::function 或单独写一个成员函数。

复杂度分析

  • 时间复杂度O(m * n * 3) = O(mn)
    每个状态 (i, j, k) 只会被计算一次,单次转移为 O(1)
  • 空间复杂度O(mn),用于存储记忆化数组。

总结

本题是经典路径 DP 的拓展,关键在于发现"豁免次数"可以作为状态的一维,然后用记忆化搜索清晰地表达出"选择豁免"与"不豁免"两种转移。

这种状态扩展的思路在许多带有"操作次数限制"的网格问题中非常常见(如最多修改 k 次、最多使用 k 次魔法等)

相关推荐
foundbug9992 小时前
基于混合整数规划的电池容量优化 - MATLAB实现
数据结构·算法·matlab
memcpy03 小时前
LeetCode 2452. 距离字典两次编辑以内的单词【暴力;字典树】中等
算法·leetcode·职场和发展
王老师青少年编程3 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【排序贪心】:魔法
c++·算法·贪心·csp·信奥赛·排序贪心·魔法
wearegogog1233 小时前
基于和差波束法的单脉冲测角MATLAB实现
人工智能·算法·matlab
AI科技星4 小时前
灵魂商数(SQ) · 全域数学统一定义【乖乖数学】
算法·机器学习·数学建模·数据挖掘·量子计算
晓觉儿4 小时前
【GPLT】2026年第十一届团队程序设计天梯赛赛后题解(已写2h,存档中)
数据结构·c++·算法·深度优先·图论
We་ct4 小时前
LeetCode 322. 零钱兑换:动态规划入门实战
前端·算法·leetcode·typescript·动态规划
6Hzlia4 小时前
【Hot 100 刷题计划】 LeetCode 394. 字符串解码 | C++ 单栈回压法
c++·算法·leetcode
穿条秋裤到处跑5 小时前
每日一道leetcode(2026.04.22):距离字典两次编辑以内的单词
算法·leetcode
淘矿人5 小时前
Claude辅助算法设计与优化
人工智能·python·算法·microsoft·github·bug·pygame