2025-07-23:机器人可以获得的最大金币数。用go语言,你有一个大小为 m 行 n 列的网格。机器人从左上角位置(0,0)开始,目标是走到右下角位置(m-1,n-1)。机器人每步只能往右边或往下边移动。
网格中每个格子有一个整数 coins[i][j]:
- 如果值 >= 0,机器人会获得对应数量的金币;
- 如果值 < 0,表示此格子有强盗,机器人会被抢走相应数量金币(即该值的绝对值)。
不过,机器人有一次特殊能力,可以在整个行程中最多“感化”2个强盗,也就是说最多有2个负值格子不会造成金币损失。
最终,求机器人在满足移动规则和特殊能力限制下,所能获得的金币最大值。
需要注意,最终金币总数可以是负数。
m == coins.length。
n == coins[i].length。
1 <= m, n <= 500。
-1000 <= coins[i][j] <= 1000。
输入: coins = [[10,10,10],[10,10,10]]。
输出: 40。
解释:
一个获得最多金币的最优路径如下:
从 (0, 0) 出发,初始金币为 10(总金币 = 10)。
移动到 (0, 1),获得 10 枚金币(总金币 = 10 + 10 = 20)。
移动到 (0, 2),再获得 10 枚金币(总金币 = 20 + 10 = 30)。
移动到 (1, 2),获得 10 枚金币(总金币 = 30 + 10 = 40)。
题目来自力扣3418。
一、动态规划状态定义
- 状态数组
f[i][j][k]
- 代表机器人走到第
i行第j列格子时,已经“感化”了k(0~2)个强盗,获得的最大金币数。
- 状态转移方向
- 由于机器人只能向右或者向下移动,所以到达
(i,j)必须来自(i-1,j)或(i,j-1)。
- 状态转移方程
对于当前格子金币数val = coins[i][j]:
- 如果
val >= 0,只需把之前状态金币加上val,感化次数不变。 - 如果
val < 0,可以选择感化强盗(即扣金币次数加1,但不扣金币数)或者不感化(扣金币数,但感化次数不增加)。 - 每种可能选择都更新对应
f[i][j][k]的最大金币数。
二、代码处理(参考代码中的思路)
注意原代码中的 f 是对 coins 按行维度的一维压缩方案,且用长度为 n+1 的数组保存当前列位置最大金币数,且第三维为“感化次数”。
具体操作:
- 初始化状态
- 把状态全部初始化为极小值(
math.MinInt / 2),表示不可达或初始不可能状态。 -
f[1] = [3]int{0,0,0},初始位置金币为0,以方便下面累加。
- 逐行处理
- 遍历每行
row,对于每个格子(列j),更新f[j+1]的3个状态:
-
f[j+1][0]:当前格子不感化负金币,累加金币或扣除金币。 -
f[j+1][1]:当前格子感化第1个负金币,更新状态。 -
f[j+1][2]:当前格子感化第2个负金币,更新状态。
- 对更新用
max函数来决定不同路径中获得的最大金币数。
- 返回结果
- 最终返回的结果是
f[n][2],即走到最后单元格感化2个强盗的最大金币。
三、具体过程举例(简化说明)
假设2x3的例子 coins=[[10,10,10],[10,10,10]]:
- 初始化状态,同一行中每个格子存储3个状态(0感化,1感化,2感化),每个状态存储最大金币。
- 第一行遍历,累加金币,因为均为正数,感化数不会增加,最大金币为
10->20->30。 - 第二行,继续累加金币,路径可能从左侧或上一行的相同列过来,因为都为正数,最大金币为
30+10=40。 - 最终机器人获得40金币。
四、时间复杂度
- 每个格子
(m*n)都需要计算3个感化状态。 - 每个状态更新时常数复杂度操作。
- 时间复杂度 = O(m * n * 3) ≈ O(m * n)。
五、空间复杂度
- 代码中用了一维的状态数组
f,长度为n+1,每个位置存储3个状态整数。 - 空间复杂度 = O(n * 3) = O(n)。
- 如果没使用压缩,就是
O(m * n * 3)。
总结
内容 | 说明 |
状态含义 |
|
状态转移 | 来源于上方或左方格子,更新感化数和金币数 |
感化次数 | 最高2次,3个状态对应0、1、2次感化 |
时间复杂度 | O(m * n) |
空间复杂度 | O(n) (代码中一维空间压缩) |
通过此方法,合理地利用状态将“感化次数”因素融入路径规划,能在线性时间和较少空间内完成搜索,适合大规模输入。
Go完整代码如下:
package main
import (
"fmt"
"math"
)
func maximumAmount(coins [][]int) int {
n := len(coins[0])
f := make([][3]int, n+1)
for j := range f {
f[j] = [3]int{math.MinInt / 2, math.MinInt / 2, math.MinInt / 2}
}
f[1] = [3]int{}
for _, row := range coins {
for j, x := range row {
f[j+1][2] = max(f[j][2]+x, f[j+1][2]+x, f[j][1], f[j+1][1])
f[j+1][1] = max(f[j][1]+x, f[j+1][1]+x, f[j][0], f[j+1][0])
f[j+1][0] = max(f[j][0], f[j+1][0]) + x
}
}
return f[n][2]
}
func main() {
coins := [][]int{{10, 10, 10}, {10, 10, 10}}
result := maximumAmount(coins)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def maximumAmount(coins):
if not coins or not coins[0]:
return 0
n = len(coins[0])
# 初始化dp数组,f[j]表示到第j列(0-indexed)的三种状态
f = [[-10**18, -10**18, -10**18] for _ in range(n+1)]
f[1] = [0, 0, 0] # 起始点初始化
for row in coins:
for j, x in enumerate(row):
# 状态转移方程
s2 = max(
f[j][2] + x, # 从左边的状态2向右移动
f[j+1][2] + x, # 从上方的状态2向下移动(连续第三次向下,但按状态2处理)
f[j][1], # 从左边的状态1转移(不连续向下)
f[j+1][1] # 从上方的状态1转移(不连续向下)
)
s1 = max(
f[j][1] + x, # 从左边的状态1向右移动
f[j+1][1] + x, # 从上方的状态1向下移动(第一次向下)
f[j][0], # 从左边的状态0转移
f[j+1][0] # 从上方的状态0转移
)
s0 = max(f[j][0], f[j+1][0]) + x # 状态0(最后一步向右)
# 更新下一列的状态
f[j+1] = [s0, s1, s2]
return f[n][2]
# 测试示例
if __name__ == "__main__":
coins = [[10, 10, 10], [10, 10, 10]]
result = maximumAmount(coins)
print(result) # 输出结果
















