状态压缩搜索解法(DFS + Dominance)

一、问题回顾

给定一个 M × N 的地图:

  • 0:障碍,不可通过
  • -1:加油站,可将油量重置为 100
  • 正整数:通过该格子的油耗

车辆从左上角 (0,0) 出发,到右下角 (M-1,N-1) 结束,可上下左右移动。

要求计算 保证能到达终点的最小初始油量 ,若无法到达,返回 -1


二、核心观察(关键建模)

1️⃣ 初始油量的真正含义

由于:

  • 加油站会 直接重置油量为 100
  • 油箱最大容量固定为 100

因此,初始油量只需要覆盖"最坏的一段连续消耗"

换句话说:

最小初始油量 = 路径上所有"连续消耗段"的最大值

而不是路径总油耗。


2️⃣ 路径可以拆分为若干"连续消耗段"

  • 遇到 -1:连续段结束,油量重置
  • 正整数:加入当前连续消耗段

问题转化为:

在所有可行路径中,最小化
max(每一段连续消耗)


三、状态设计

我们在 DFS 中维护如下状态:

arduino 复制代码
(y, x, min, used)

含义:

  • (y, x):当前位置
  • used:当前连续消耗段的累计油耗
  • min:到目前为止,路径上出现的 最大连续消耗段(候选答案)

终点处的答案即为:

arduino 复制代码
min === -1 ? used : min

(处理"全程无加油站"的情况)


四、剪枝与状态压缩(核心优化)

1️⃣ 基本可行性剪枝

arduino 复制代码
if (used > 100 || min > 100) return;
if (min > res) return;
  • 超过油箱上限,路径必死
  • 已经不可能优于当前最优解,直接剪枝

2️⃣ Dominance(支配)剪枝 ------ 关键创新点

对每个 (y,x),我们只保留不被支配的状态

定义 dominance 关系:

若存在历史状态 (bMin, bUsed),使得
bMin ≤ minbUsed ≤ used

则当前状态 必然更差,可安全剪枝

实现方式:

ini 复制代码
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 前沿


五、完整算法流程

  1. (0,0) 开始 DFS
  2. 维护 (min, used) 状态
  3. 遇到加油站重置 used
  4. 使用 dominance 剪枝压缩状态空间
  5. 到达终点更新答案

六、时间复杂度分析

状态规模对比

方法 每个格子状态数 总状态数
Dijkstra(fuel 维度) O(100) O(M·N·100)
本解法(Pareto 前沿) O(1)(极少) O(M·N)

总复杂度

scss 复制代码
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)

ini 复制代码
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
相关推荐
会编程的土豆1 天前
c语言时间戳从入门到精通
linux·c语言·算法
所谓伊人,在水一方3331 天前
【机器学习精通】第2章 | 优化算法深度解析:从梯度下降到自适应优化器
人工智能·python·算法·机器学习·信息可视化
Storynone1 天前
【Day24】LeetCode:122. 买卖股票的最佳时机 II,55. 跳跃游戏,45. 跳跃游戏II,1005. K次取反后最大化的数组和
python·算法·leetcode
滴滴答滴答答1 天前
机考刷题之 17&18&19&20&21&22 LeetCode 1248&121&43&93&62&63
算法·leetcode·职场和发展
for_ever_love__1 天前
Objective-C学习 类别和扩展
学习·算法·objective-c
Sakinol#1 天前
Leetcode Hot 100 ——回溯part02
算法·leetcode
ArturiaZ1 天前
【day53】
开发语言·c++·算法
历程里程碑1 天前
36 Linux线程池实战:日志与策略模式解析
开发语言·数据结构·数据库·c++·算法·leetcode·哈希算法
可编程芯片开发1 天前
基于自适应MUSIC算法的波束形成matlab仿真
算法·matlab·波束形成·自适应music
2301_789015621 天前
DS进阶:红黑树
c语言·开发语言·数据结构·c++·算法·r-tree·lsm-tree