题目
有一辆汽车需要从 m * n 的地图的左上角(起点)开往地图的右下角(终点 ),去往每一个地区都需要消耗一定的油量,加油站可进行加油
请你计算汽车确保从起点到达终点时所需的最少初始油量
说明:
(1)智能汽车可以上下左右四个方向移动;
(2)地图上的数字取值是 0 或 −1 或者正整数;
−1:表示加油站,可以加满油,汽车的油箱容量最大为 100;
0 :表示这个地区是障碍物,汽车不能通过;
正整数:表示汽车走过这个地区的耗油量
(3)如果汽车无论如何都无法到达终点,则返回 −1
输入描述
第一行为两个数字,M , N,表示地图的大小为 M * N ( 0 < M,N ≤ 200 )
后面一个M * N 的矩阵,其中的值是 0 或 −1 或正整数,加油站的总数不超过 200个
输出描述
如果汽车无论如何都无法到达终点,则返回-1
如果汽车可以到达终点,则返回最少的初始油量
样例
输入
2 2
10 20
30 40
输出
70
说明
行走的路线为:右 -> 下
理解
1.因为存在加油站和障碍物,所以初始油量取决于路径,因此探索过程采用二分法确定初始油量逐步探索,减少探索次数
2.题目是需要最少油量,所以探索过程需要探索最优秀路线,以样例为例
(0,0)需要10 ,(0,1)需要20 ,(1,0)需要30 ,(1,1)需要40,(0,1)和(1,0)代表不同路径,(1,1)是共同经过的点,则在经过1,1的时候应该做出对比,发现 (0,0),(0,1),(1,1)的路线比(0,0),(1,0),(1,1)省油,则(0,0),(1,0),(1,1)的路径应该放弃,即:每次探索都探索各自的探索方向,将下一个可探索的方向收集起来,收集的过程要判断是否已被别的路径探索过,如果有,则比较路径优劣
代码
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Scanner;
public class Test025 {
/*
* 有一辆汽车需要从 m * n 的地图的左上角(起点)开往地图的右下角(终点 ),去往每一个地区都需要消耗一定的油量,加油站可进行加油
* 请你计算汽车确保从起点到达终点时所需的最少初始油量
* 说明:
* (1)智能汽车可以上下左右四个方向移动;
* (2)地图上的数字取值是 0 或 −1 或者正整数;
* −1:表示加油站,可以加满油,汽车的油箱容量最大为 100;
* 0 :表示这个地区是障碍物,汽车不能通过;
* 正整数:表示汽车走过这个地区的耗油量
* (3)如果汽车无论如何都无法到达终点,则返回 −1
*
* */
// 探索位移的方向
static int[] DX = {0, 1, 0, -1};
static int[] DY = {1, 0, -1, 0};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int[] rc = Arrays.stream(scanner.nextLine().split(" ")).mapToInt(Integer::parseInt).toArray();
int row = rc[0];
int col = rc[1];
// 矩阵信息图
int[][] mapArr = new int[row][col];
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
mapArr[i][j] = scanner.nextInt();
}
}
if (mapArr[0][0] == 0 || mapArr[row - 1][col - 1] == 0) {
// 起点或终点是障碍物,不可达到
System.out.println(-1);
return;
}
// 最低油量
int min = 0;
// 最多油量
int max = 100;
// 最少油量
int least = -1;
// 定义携带油量 carry
int carry = (min + max) / 2;
// 二分查找 当最少等于最多时,则探索完成
while (min <= max) {
// 判断当前油量是否能完成探索
if (checkEnough(mapArr, carry, row, col)) {
least = carry;
max = carry - 1;
} else {
min = carry + 1;
}
carry = (min + max) / 2;
}
System.out.println(least);
}
private static boolean checkEnough(int[][] mapArr, int carry, int n, int m) {
// 存储访问探索过的节点
boolean[][] visited = new boolean[n][m];
// 存储剩余油量
int[][] remainArr = new int[n][m];
// 读取矩阵的每一个元素
for (int i = 0; i < n; i++) {
Arrays.fill(remainArr[i], -1);
}
// 移动到第一格剩余油量
remainArr[0][0] = carry - mapArr[0][0];
// 如果剩余油量负数,则说明到达不了,返回
if (remainArr[0][0] < 0) {
return false;
}
if (mapArr[0][0] == -1) {
remainArr[0][0] = 100;
}
// 使用优先队列存储状态 顺序剩余油量少者优先
PriorityQueue<int[]> q = new PriorityQueue<>((a, b) -> b[0] - a[0]);
q.offer(new int[] {remainArr[0][0], 0, 0}); // 将初始状态加入队列
while (!q.isEmpty()) {
int[] cur = q.poll();
int curX = cur[1];
int curY = cur[2];
if (visited[curX][curY]) {
continue;
}
int curRemain = cur[0];
visited[curX][curY] = true;
for (int i = 0; i < 4; i++) {
int nextX = curX + DX[i];
int nextY = curY + DY[i];
// 越界
// 下一个地点是障碍物 mapArr[nextX][nextY] == 0
// 下一个地点探索过 visited[nextX][nextY]
// 下一个地点 剩余油量不足到达
if (nextX < 0 || nextX >= n || nextY < 0 || nextY >= m
|| mapArr[nextX][nextY] == 0
|| visited[nextX][nextY]
|| mapArr[nextX][nextY] > curRemain) {
continue;
}
// 到达终点
if (nextX == n - 1 && nextY == m - 1) {
return true;
}
// 探索此点剩余油量
int remain = mapArr[nextX][nextY] == -1 ? 100 : curRemain - mapArr[nextX][nextY];
// 需注意,remainArr[nextX][nextY] 可能已经被前面的路径探索过,所以对比路径优劣
// 如果此路径剩余油量比前面路径多,则继续探索此路径
if (remain > remainArr[nextX][nextY]) {
remainArr[nextX][nextY] = remain;
q.offer(new int[] {remainArr[nextX][nextY], nextX, nextY});
}
}
}
return visited[n - 1][m - 1];
}
}