一、问题回顾
给定一个 M × N 的地图:
0:障碍,不可通过-1:加油站,可将油量重置为 100- 正整数:通过该格子的油耗
车辆从左上角 (0,0) 出发,到右下角 (M-1,N-1) 结束,可上下左右移动。
要求计算 保证能到达终点的最小初始油量,若无法到达,返回 -1。
二、核心观察(关键建模)
1️⃣ 初始油量的真正含义
由于:
- 加油站会 直接重置油量为 100
- 油箱最大容量固定为 100
因此,初始油量只需要覆盖“最坏的一段连续消耗” 。
换句话说:
最小初始油量 = 路径上所有“连续消耗段”的最大值
而不是路径总油耗。
2️⃣ 路径可以拆分为若干“连续消耗段”
- 遇到
-1:连续段结束,油量重置 - 正整数:加入当前连续消耗段
问题转化为:
在所有可行路径中,最小化
max(每一段连续消耗)
三、状态设计
我们在 DFS 中维护如下状态:
(y, x, min, used)
含义:
(y, x):当前位置used:当前连续消耗段的累计油耗min:到目前为止,路径上出现的 最大连续消耗段(候选答案)
终点处的答案即为:
min === -1 ? used : min
(处理“全程无加油站”的情况)
四、剪枝与状态压缩(核心优化)
1️⃣ 基本可行性剪枝
if (used > 100 || min > 100) return;
if (min > res) return;
- 超过油箱上限,路径必死
- 已经不可能优于当前最优解,直接剪枝
2️⃣ Dominance(支配)剪枝 —— 关键创新点
对每个 (y,x),我们只保留不被支配的状态。
定义 dominance 关系:
若存在历史状态
(bMin, bUsed),使得
bMin ≤ min且bUsed ≤ used
则当前状态 必然更差,可安全剪枝
实现方式:
const [bMin, bUsed] = best[y][x];
if (min !== -1 && min > bMin || min === -1 && used > bMin) return;
if (min === bMin && used >= bUsed) return;
best[y][x] = [min === -1 ? bMin : min, used];
为什么这是安全的?
min是最终优化目标(越小越好)used只影响后续可行性(越小越好)- 被支配状态 在未来不可能产生更优结果
这相当于只保留 Pareto 前沿。
五、完整算法流程
- 从
(0,0)开始 DFS - 维护
(min, used)状态 - 遇到加油站重置
used - 使用 dominance 剪枝压缩状态空间
- 到达终点更新答案
六、时间复杂度分析
状态规模对比
| 方法 | 每个格子状态数 | 总状态数 |
|---|---|---|
| Dijkstra(fuel 维度) | O(100) | O(M·N·100) |
| 本解法(Pareto 前沿) | O(1)(极少) | O(M·N) |
总复杂度
O(M × N)
在 M,N ≤ 200 的限制下,对 JavaScript 非常友好。
七、与 Dijkstra 解法的对比
| 维度 | Dijkstra | 本解法 |
|---|---|---|
| 状态建模 | (x,y,fuel) | (x,y,min,used) |
| 状态数 | Θ(M·N·100) | Θ(M·N) |
| 数据结构 | 优先队列 | DFS + 数组 |
| JS 性能 | 中等 | 优秀 |
| 通用性 | 高 | 题目特化 |
本解法利用了“油箱上限 + 加油站重置”的结构性约束,
在该问题下 状态空间严格小于 Dijkstra。
八、完整代码(DFS + Dominance)
const rl = require("readline").createInterface({ input: process.stdin });
let lines = [];
rl.on("line", l => lines.push(l)).on("close", () => {
const [M, N] = lines[0].split(',').map(Number);
let grid = lines.slice(1).map(r => r.split(',').map(Number));
let res = Infinity;
let vis = Array.from({ length: M }, () => Array(N).fill(false));
let best = Array.from({ length: M }, () =>
Array.from({ length: N }, () => [Infinity, Infinity])
);
const dir = [[1,0],[0,1],[0,-1],[-1,0]];
function dfs(y, x, min, used) {
if (used > 100 || min > 100 || min > res) return;
const [bMin, bUsed] = best[y][x];
if (min !== -1 && min > bMin || min === -1 && used > bMin) return;
if (min === bMin && used >= bUsed) return;
best[y][x] = [min === -1 ? bMin : min, used];
if (y === M - 1 && x === N - 1) {
res = Math.min(res, min === -1 ? used : min);
return;
}
for (let [dy, dx] of dir) {
let ny = y + dy, nx = x + dx;
if (ny < 0 || ny >= M || nx < 0 || nx >= N) continue;
if (vis[ny][nx] || grid[ny][nx] === 0) continue;
vis[ny][nx] = true;
if (grid[ny][nx] === -1) {
dfs(ny, nx, min === -1 ? used : min, 0);
} else {
dfs(ny, nx, min, used + grid[ny][nx]);
}
vis[ny][nx] = false;
}
}
vis[0][0] = true;
dfs(0, 0, -1, Math.max(grid[0][0], 0));
console.log(res === Infinity ? -1 : res);
});
九、总结
- 这是一次 问题特化下的状态压缩优化
- 利用 Pareto dominance 将搜索空间压缩到极限
- 在 JS 环境下 性能严格优于通用 Dijkstra