leetcode
leetcode 1451 ~ 1500
最小体力消耗路径

最小体力消耗路径

难度:

标签:

题目描述

你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往  四个方向之一移动,你想要找到耗费 体力 最小的一条路径。

一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。

请你返回从左上角走到右下角的最小 体力消耗值 。

 

示例 1:

输入:heights = [[1,2,2],[3,8,2],[5,3,5]]
输出:2
解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。

示例 2:

输入:heights = [[1,2,3],[3,8,4],[5,3,5]]
输出:1
解释:路径 [1,2,3,4,5] 的相邻格子差值绝对值最大为 1 ,比路径 [1,3,5,3,5] 更优。

示例 3:

输入:heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]]
输出:0
解释:上图所示路径不需要消耗任何体力。

 

提示:

  • rows == heights.length
  • columns == heights[i].length
  • 1 <= rows, columns <= 100
  • 1 <= heights[i][j] <= 106

代码结果

运行时间: 334 ms, 内存: 17.3 MB


/*
 * Problem: Find the minimum effort required to travel from the top-left to the bottom-right corner of a grid. 
 * The effort is defined by the maximum absolute difference in heights between two adjacent cells on the path.
 * Solution Approach: This solution uses the same approach as the previous one but leverages Java Streams.
 */
import java.util.*;
import java.util.stream.*;

class SolutionStream {
    public int minimumEffortPath(int[][] heights) {
        int rows = heights.length;
        int cols = heights[0].length;
        int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        int[][] effort = IntStream.range(0, rows).mapToObj(i -> IntStream.range(0, cols).map(j -> Integer.MAX_VALUE).toArray()).toArray(int[][]::new);
        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
        pq.offer(new int[]{0, 0, 0}); // effort, row, col
        effort[0][0] = 0;
        while (!pq.isEmpty()) {
            int[] current = pq.poll();
            int currentEffort = current[0];
            int row = current[1];
            int col = current[2];
            if (row == rows - 1 && col == cols - 1) {
                return currentEffort;
            }
            Arrays.stream(directions).forEach(direction -> {
                int newRow = row + direction[0];
                int newCol = col + direction[1];
                if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) {
                    int nextEffort = Math.max(currentEffort, Math.abs(heights[newRow][newCol] - heights[row][col]));
                    if (nextEffort < effort[newRow][newCol]) {
                        effort[newRow][newCol] = nextEffort;
                        pq.offer(new int[]{nextEffort, newRow, newCol});
                    }
                }
            });
        }
        return 0; // Unreachable code, since the destination will always be reached
    }
}

解释

方法:

该题解采用的是一个优化的 Dijkstra 算法,通过一个最小堆来维持当前探索的路径中的最大高度差的最小值。首先,定义一个二维数组 `dps` 来存储到每个点的最小体力消耗值,并初始化终点的体力消耗值为0。使用优先队列(最小堆)来按体力消耗递增的顺序处理每个格子。对于当前格子,我们检查它的四个邻居,如果这个邻居没有被访问过,并且通过当前格子到该邻居的路径可以得到更小的体力消耗值,我们就更新邻居的体力消耗值,并将其加入堆中。这个过程重复,直到堆为空,或者我们访问到起点。最终,起点的 `dps` 值就是我们要找的答案。

时间复杂度:

O((MN) * log(MN))

空间复杂度:

O(MN)

代码细节讲解

🦆
在该算法中,如何保证每次从堆中弹出的节点确实是当前路径中体力消耗最小的节点?
在优化的Dijkstra算法中,使用最小堆(或优先队列)确保每次从堆中弹出的是具有最小体力消耗值的节点。堆的性质是保证堆顶元素是堆中最小的元素,因此每次从堆中弹出的节点是当前所有待处理节点中体力消耗值最小的。这种结构自然而然地按照体力消耗值的升序来处理节点,从而确保探索过程是贪心地选择最小体力消耗的路径。
🦆
优化的 Dijkstra 算法在处理每个节点时如何确定是否需要更新邻居节点的体力消耗值?
在优化的Dijkstra算法中,对于当前节点的每个邻居,算法首先计算从当前节点到该邻居节点通过直接路径的体力消耗值。如果这个新计算的体力消耗值小于邻居节点在dps数组中已存储的体力消耗值,则更新邻居节点的体力消耗值,并将邻居节点加入到堆中以便进一步探索。这样的更新确保了只有当找到一条更优(即体力消耗更小)的路径到达该邻居时,才对其进行更新。
🦆
算法实现中提到了`如果当前计算的消耗不优于已有的,则跳过`,请问这种情况下的'不优于'是如何定义的?
'不优于'在本算法中意味着新计算的体力消耗值不小于在dps数组中已经记录的到那个节点的体力消耗值。换句话说,如果通过当前节点到达其邻居的体力消耗值大于或等于已知到达该邻居的最小体力消耗值,则无需更新,因为这不会改善到达那个邻居的最优路径。
🦆
为什么在实现中初始化堆时,只将终点(`[0, n-1, m-1]`)加入堆中,而不是起点或其他任何点?
在本题解中,算法从终点开始反向寻找到起点的最小体力消耗路径。这是一种常见的路径搜索优化技术,特别是在目标导向的路径搜索中,从终点开始可以更直接地探索到起点的最优路径。初始化堆时将终点加入是为了从终点开始这一反向搜索,堆中的元素代表当前待处理的节点,其体力消耗值初始化为0,因为从终点到终点的消耗自然是0。这样做有助于简化算法逻辑和实现。

相关问题