题目描述
有一辆汽车需要从 m * n 的地图左上角(起点)开往地图的右下角(终点),去往每一个地区都需要消耗一定的油量,加油站可进行加油。
请你计算汽车确保从从起点到达终点时所需的最少初始油量。
说明:
智能汽车可以上下左右四个方向移动
地图上的数字取值是 0 或 -1 或 正整数:
-1 :表示加油站,可以加满油,汽车的油箱容量最大为100;
0 :表示这个地区是障碍物,汽车不能通过
正整数:表示汽车走过这个地区的耗油量
如果汽车无论如何都无法到达终点,则返回 -1
输入描述
第一行为两个数字,M,N,表示地图的大小为 M * N
0 < M,N ≤ 200
后面一个 M * N 的矩阵,其中的值是 0 或 -1 或正整数,加油站的总数不超过 200 个
输出描述
如果汽车无论如何都无法到达终点,则返回 -1
如果汽车可以到达终点,则返回最少的初始油量
示例1
输入
2,2
10,20
30,40
输出
70
说明
行走的路线为:右→下
示例2
输入
4,4
10,30,30,20
30,30,-1,10
0,20,20,40
10,-1,30,40
输出
70
说明
行走的路线为:右→右→下→下→下→右
示例3
输入
4,5
10,0,30,-1,10
30,0,20,0,20
10,0,10,0,30
10,-1,30,0,10
输出
60
说明
行走的路线为:下→下→下→右→右→上→上→上→右→右→下→下→下
示例4
输入
4,4
10,30,30,20
30,30,20,10
10,20,10,40
10,20,30,40
输出
-1
说明
无论如何都无法到达终点
思路
核心逻辑
采用逆向Dijkstra算法(优先级队列+BFS+动态规划) 求解:
- 逆向视角:将“起点到终点的最小初始油量”转化为“终点到起点的最小油耗”,规避正向遍历中初始油量未知、加油状态复杂的问题;
- 动态规划定义:
dp[x][y]表示从(x,y)到终点的最小油耗(包含自身消耗),初始时终点dp值为自身耗油量(加油站则为0),其余为无穷大; - 优先级队列(贪心):按油耗从小到大取出节点,确保每次处理的是当前最优路径,遍历四个方向的邻居节点;
- 状态更新:
- 邻居为加油站(-1):油耗重置为0(正向加油后油量满100,逆向出发油耗为0);
- 邻居为普通格子:油耗为自身耗油量 + 当前节点到终点的油耗,超过油箱容量100则跳过;
- 若新油耗更小,更新
dp并将节点加入队列;
- 终止条件:到达起点时返回油耗(≤100),队列遍历完仍未到起点则返回-1。
时空复杂度
- 时间复杂度:O(M×N×log(M×N)),其中M、N为地图行列数;每个节点最多入队一次,优先级队列排序耗时为O(log(M×N)),整体遍历所有可达节点为O(M×N);
- 空间复杂度:O(M×N),主要用于存储
dp数组和优先级队列(最多存储所有可达节点)。
关键优化
- 逆向遍历简化加油逻辑:加油站直接重置油耗为0,无需处理正向的油量加满状态;
- 优先级队列保证最优性:每次处理油耗最小的节点,避免无效的高油耗路径遍历;
- 剪枝策略:油耗超过油箱容量100时直接跳过,减少无效计算。
代码
function calculateMinInitialOil(input) {
const lines = input.trim().split('\n');
const [m, n] = lines[0].split(',').map(Number);
const grid = [];
for (let i = 1; i <= m; i++) {
grid.push(lines[i].split(',').map(Number));
}
// 终点坐标
const endX = m - 1;
const endY = n - 1;
// 检查终点是否为障碍物
if (grid[endX][endY] === 0) return -1;
// 方向数组:上下左右
const dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]];
// dp[x][y]:从(x,y)到终点的最小油耗(包含(x,y)自身的消耗)
const dp = Array.from({ length: m }, () => Array(n).fill(Infinity));
// 初始化终点:自身油耗(加油站的话消耗0,因为加油不消耗且重置)
dp[endX][endY] = grid[endX][endY] === -1 ? 0 : grid[endX][endY];
// 优先队列:[油耗, x, y],按油耗从小到大排序
const pq = [[dp[endX][endY], endX, endY]];
while (pq.length > 0) {
// 取出油耗最小的节点
pq.sort((a, b) => b[0] - a[0]);
const [currentCost, x, y] = pq.pop();
// 若当前油耗已大于记录的最优值,跳过
if (currentCost > dp[x][y]) continue;
// 到达起点,返回结果(需≤100)
if (x === 0 && y === 0) {
return currentCost <= 100 ? currentCost : -1;
}
// 探索四个方向的邻居(反向移动,即正向中邻居→当前节点)
for (const [dx, dy] of dirs) {
const nx = x + dx;
const ny = y + dy;
// 边界检查
if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
// 障碍物检查
if (grid[nx][ny] === 0) continue;
let newCost;
if (grid[nx][ny] === -1) {
// 加油站:自身消耗0(加油不消耗),且到终点的油耗为0(因为加满后从这里出发)
newCost = 0;
} else {
// 普通格子:自身油耗 + 从当前节点(x,y)到终点的油耗
newCost = grid[nx][ny] + currentCost;
// 超过油箱容量100,无法通过
if (newCost > 100) continue;
}
// 更新最优油耗
if (newCost < dp[nx][ny]) {
dp[nx][ny] = newCost;
pq.push([newCost, nx, ny]);
}
}
}
// 起点不可达
return -1;
}
// 测试用例验证
const testCases = [
{
input: `2,2
10,20
30,40`,
expected: 70
},
{
input: `4,4
10,30,30,20
30,30,-1,10
0,20,20,40
10,-1,30,40`,
expected: 70
},
{
input: `4,5
10,0,30,-1,10
30,0,20,0,20
10,0,10,0,30
10,-1,30,0,10`,
expected: 60
},
{
input: `4,4
10,30,30,20
30,30,20,10
10,20,10,40
10,20,30,40`,
expected: -1
}
];
// 执行测试
testCases.forEach((test, index) => {
console.log(`=== 测试用例 ${index + 1} ===`);
const result = calculateMinInitialOil(test.input);
console.log(`输入:\n${test.input}`);
console.log(`预期输出: ${test.expected}`);
console.log(`实际输出: ${result}`);
console.log(result === test.expected ? '测试通过' : '测试失败');
console.log('\n');
});
743

被折叠的 条评论
为什么被折叠?



