1. 题目描述:矩阵海拔行走 (Base Version)
题目内容 :
小红拿到了一个 n×mn \times mn×m 的二维矩阵,矩阵的每个元素代表该位置的海拔高度(正整数)。小红从左上角 (0,0)(0, 0)(0,0) 出发,目标是到达右下角 (n−1,m−1)(n-1, m-1)(n−1,m−1)。
她每一步可以向上下左右四个方向移动。移动的代价 定义为:当前格子海拔与目标格子海拔之差的绝对值 。
请问:小红到达终点所需的最少体力消耗是多少?
输入限制:
- n,m≤500n, m \le 500n,m≤500
- 矩阵元素 ≤106\le 10^6≤106
2. Java 标准实现
这份代码包含了我们之前讨论的所有关键点:极大值初始化 、优先队列排序 以及出队校验(懒惰删除)。
java
import java.util.*;
public class DijkstraMatrix {
/**
* Dijkstra 核心算法
* @param graph 输入的二维矩阵
* @return 从左上角到右下角的最短路径长度
*/
public int solve(int[][] graph) {
if (graph == null || graph.length == 0) return 0;
int n = graph.length;
int m = graph[0].length;
// 1. 距离矩阵初始化为无穷大
int[][] dist = new int[n][m];
for (int i = 0; i < n; i++) {
Arrays.fill(dist[i], Integer.MAX_VALUE);
}
// 2. 优先队列:按当前总代价从小到大排序 [row, col, time]。
//队列中存放的是消息,每当节点X的临近节点Y认为,从(0,0)经过Y到达X耗时更少时,就会更新dist矩阵,并发送一个关于到达X耗时的消息到pq中。
//由于pq中可能同时存在多个针对X的消息,因此pq可能是历史某节点发送的,而dist为真正最后一个节点落地的
//所以存在关系dist[x][y]<=pq.poll()[2]
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[2]));
// 3. 起点初始化
dist[0][0] = 0;
pq.offer(new int[]{0, 0, 0});
int[] dr = {0, 0, 1, -1};
int[] dc = {1, -1, 0, 0};
while (!pq.isEmpty()) {
int[] cur = pq.poll();
int x = cur[0];
int y = cur[1];
int d = cur[2];
// 4. 真理时刻:校验出队的消息是否过时
if (d > dist[x][y]) continue;
// 5. 终点提前返回优化
if (x == n - 1 && y == m - 1) return d;
// 6. 探索四周
for (int i = 0; i < 4; i++) {
int nr = x + dr[i];
int nc = y + dc[i];
if (nr >= 0 && nr < n && nc >= 0 && nc < m) {
int cost = Math.abs(graph[nr][nc] - graph[x][y]);
// 当前节点发现经过自己到达某周围节点的距离更短,于是更新周围节点距离,并发送消息
if (d + cost < dist[nr][nc]) {
dist[nr][nc] = d + cost;
pq.offer(new int[]{nr, nc, dist[nr][nc]});
}
}
}
}
return dist[n - 1][m - 1];
}
}
3. 测试脚本 (JUnit 思想实现)
这个脚本包含三个典型用例:基础路径、障碍绕行(高海拔墙)、以及平地移动。
java
public class TestDijkstra {
public static void main(String[] args) {
DijkstraMatrix solver = new DijkstraMatrix();
// --- 用例 1:基础 3x3 绕行 ---
int[][] case1 = {
{1, 10, 10},
{2, 3, 10},
{10, 4, 1}
};
// 路径:(0,0)->(1,0)->(1,1)->(2,1)->(2,2)
// 代价:|1-2| + |2-3| + |3-4| + |4-1| = 1 + 1 + 1 + 3 = 6
assertTest(1, solver.solve(case1), 6);
// --- 用例 2:平原(代价为 0) ---
int[][] case2 = {
{5, 5, 5},
{5, 5, 5}
};
assertTest(2, solver.solve(case2), 0);
// --- 用例 3:大跨度落差 ---
int[][] case3 = {
{1, 100},
{100, 1}
};
// 路径:(0,0)->(0,1)->(1,1) = 99 + 99 = 198
// 路径:(0,0)->(1,0)->(1,1) = 99 + 99 = 198
assertTest(3, solver.solve(case3), 198);
System.out.println("\n🎉 所有测试用例通过!基础 Dijkstra 逻辑稳固。");
}
private static void assertTest(int id, int actual, int expected) {
if (actual == expected) {
System.out.println("Test Case " + id + ": Passed! (Result: " + actual + ")");
} else {
System.err.println("Test Case " + id + ": FAILED! (Expected: " + expected + ", but got: " + actual + ")");
System.exit(1);
}
}
}
4. 深度复盘:你学到了什么?
- 初始化 :如果不把
dist填满Integer.MAX_VALUE,程序会因为0是最小值而无法更新任何点。 - PQ 比较器 :
Comparator.comparingInt(a -> a[2])确保了"贪心"策略的正确性,先处理当前总代价最小的点。 - 懒惰删除 :
if (d > dist[x][y]) continue;解决了队列滞后性带来的冗余计算,是性能优化的关键。 - 无后效性 :因为绝对值差永远 ≥0\ge 0≥0,所以 Dijkstra 才能保证第一个出队的终点就是最优解。