黄金矿工 图解题解
这道题到底在问什么
- 输入
- [[0,6,0],[5,8,7],[0,9,0]]
- 输出
- 24(路径 9 → 8 → 7)
最优解:一步一步想明白
- 3记住「踩格记金 → 置 0 标记 → 四向取最大 → 退格还原」。下面在官方样例上,从黄金 9 这个起点一步步走。
- 4先扫一遍:网格里一共 5 个有黄金的格(土黄底色,可踩),写 0 的是空格(灰底,不能进)。题目允许从任意金格出发,所以每个金格都要各当一次起点试一遍。
- 5这里挑收益最高的起点 黄金 9(紫色)演示整条 DFS 搜索,看它一路能挖到多少黄金。
- 6踩进格子 (2,1),挖到黄金 9:把它临时置 0(紫色 = 正踩、黄色 = 路径已占用),本趟这格不能再进。当前路径金子累计 9,继续往没踩过的邻居探。
- 7踩进格子 (1,1),挖到黄金 8:把它临时置 0(紫色 = 正踩、黄色 = 路径已占用),本趟这格不能再进。当前路径金子累计 17,继续往没踩过的邻居探。
- 8踩进格子 (0,1),挖到黄金 6:把它临时置 0(紫色 = 正踩、黄色 = 路径已占用),本趟这格不能再进。当前路径金子累计 23,继续往没踩过的邻居探。
- 9格子 (0,1) 的上下左右要么是空格 0、要么已被本趟踩过,无路可走。这条分支到此结束,它能贡献的黄金就是路径上的 6,准备回溯。
- 10从 (0,1) 退出:把它的金子 6 还原回去(撤销那个置 0 的标记),这样别的走法还能踩到它。退回上一格,继续试它下一个方向。
- 11踩进格子 (1,2),挖到黄金 7:把它临时置 0(紫色 = 正踩、黄色 = 路径已占用),本趟这格不能再进。当前路径金子累计 24,继续往没踩过的邻居探。
- 12格子 (1,2) 的上下左右要么是空格 0、要么已被本趟踩过,无路可走。这条分支到此结束,它能贡献的黄金就是路径上的 7,准备回溯。
- 13从 (1,2) 退出:把它的金子 7 还原回去(撤销那个置 0 的标记),这样别的走法还能踩到它。退回上一格,继续试它下一个方向。
- 14踩进格子 (1,0),挖到黄金 5:把它临时置 0(紫色 = 正踩、黄色 = 路径已占用),本趟这格不能再进。当前路径金子累计 22,继续往没踩过的邻居探。
- 15格子 (1,0) 的上下左右要么是空格 0、要么已被本趟踩过,无路可走。这条分支到此结束,它能贡献的黄金就是路径上的 5,准备回溯。
- 16从 (1,0) 退出:把它的金子 5 还原回去(撤销那个置 0 的标记),这样别的走法还能踩到它。退回上一格,继续试它下一个方向。
- 17格 (1,1) 的四个方向都试完了,里面最大的一条分支贡献 7。于是本格能挖的黄金 = 自己的 8 + 最大分支 7 = 15,把这个值往上一层返回。
- 18格 (2,1) 的四个方向都试完了,里面最大的一条分支贡献 15。于是本格能挖的黄金 = 自己的 9 + 最大分支 15 = 24,把这个值往上一层返回。
- 19刚才只是其中一个起点。换从顶上的 黄金 6(紫色)出发,把它当起点再跑一遍 DFS,看能不能挖得比 24 更多。
- 20踩进 (1,1),挖到黄金 8,这条路累计 14。继续往下探。
- 21踩进 (2,1),挖到黄金 9,这条路累计 23。它在底行,四周已经没有能继续接上的金格了,这条路到此为止,准备结算。
- 22从 6 出发最多挖到 6 + 8 + 9 = 23,比之前从 9 出发的 24 还少 1,所以不刷新答案。这就是「每个金格都当起点、取所有结果最大值」的意义。
- 23从黄金 9 出发的这条线,9 → 8 → 7(绿色)收集到 9 + 8 + 7 = 24 黄金。把每个金格各当起点跑一遍后,没有比它更高的,所以答案是 24。
⚠️ 容易写错的地方
✗ 错:踩过的格子不还原
✓ 对:递归返回后必须把当前格金子还原
不还原 = 这格被本趟永久占用,别的起点 / 别的走法再也踩不到它,会漏掉更优路径
✗ 错:只从第一个金格出发
✓ 对:每个非零格都要各当一次起点
题目说从任意格出发,最优路径的起点不一定是第一个,漏试起点会算小
✗ 错:空格也往里递归
✓ 对:进入时先判 grid 值是否为 0
0 是空格不能踩,不剪掉会把空格当路径一部分、结果出错
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def getMaximumGold(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or r >= m or c < 0 or c >= n or grid[r][c] == 0:
return 0
gold = grid[r][c]
grid[r][c] = 0
best = max(dfs(r+1,c), dfs(r-1,c), dfs(r,c+1), dfs(r,c-1))
grid[r][c] = gold
return gold + best
return max(dfs(r, c) for r in range(m) for c in range(n))C++
#include <vector>
#include <functional>
#include <algorithm>
using namespace std;
class Solution {
public:
int getMaximumGold(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
function<int(int,int)> dfs = [&](int r, int c) {
if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0) return 0;
int gold = grid[r][c];
grid[r][c] = 0;
int best = max(max(dfs(r+1,c), dfs(r-1,c)), max(dfs(r,c+1), dfs(r,c-1)));
grid[r][c] = gold;
return gold + best;
};
int ans = 0;
for (int r = 0; r < m; ++r) for (int c = 0; c < n; ++c) ans = max(ans, dfs(r, c));
return ans;
}
};Java
import java.util.*;
class Solution {
int[][] grid; int m, n;
public int getMaximumGold(int[][] grid) {
this.grid = grid; m = grid.length; n = grid[0].length;
int ans = 0;
for (int r = 0; r < m; r++) for (int c = 0; c < n; c++) ans = Math.max(ans, dfs(r, c));
return ans;
}
private int dfs(int r, int c) {
if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0) return 0;
int gold = grid[r][c]; grid[r][c] = 0;
int best = Math.max(Math.max(dfs(r+1,c), dfs(r-1,c)), Math.max(dfs(r,c+1), dfs(r,c-1)));
grid[r][c] = gold;
return gold + best;
}
}复杂度
时间
O(m·n·4^g)
g = 非零金格数;每个格当起点、每步最多四向延伸
空间
O(g)
递归栈深度最多为一条路径的长度 g
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 黄金矿工 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题为什么敢用指数级的回溯,不会超时吗?+
看数据范围:题目保证非零金格数量 ≤ 25、网格 ≤ 15×15;搜索空间被这个上限压住,再加上「踩到空格 / 越界 / 已踩过」立即剪枝,实际探索的分支远小于理论上界,足以通过。范围小 + 起点不固定 + 不能重复访问,是「敢上回溯」的典型信号。
为什么直接改原网格(置 0 再还原),而不是另开一个 visited 数组?+
把走过的格临时置 0,等于一个状态同时表达了「空格」和「本趟已踩」两种不可进入的情况,进入判断只需一句 grid 值是否为 0,省掉一个布尔矩阵、代码更短。关键是递归返回后一定要还原,否则会污染别的分支。当然另开 visited 数组也完全正确,只是更占空间。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 黄金矿工 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。