leetcode
leetcode 951 ~ 1000
得分最高的路径

得分最高的路径

难度:

标签:

题目描述

代码结果

运行时间: 278 ms, 内存: 21.2 MB


import java.util.PriorityQueue;
import java.util.stream.IntStream;

class Solution {
    // 定义一个方向数组,表示四个方向(上,下,左,右)
    private static final int[][] DIRECTIONS = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    public int maximumMinimumPath(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        boolean[][] visited = new boolean[m][n];
        // 最大堆,按照路径上最小值排序
        PriorityQueue<int[]> maxHeap = new PriorityQueue<>((a, b) -> b[0] - a[0]);
        maxHeap.offer(new int[]{grid[0][0], 0, 0});
        visited[0][0] = true;
        while (!maxHeap.isEmpty()) {
            int[] current = maxHeap.poll();
            int score = current[0], x = current[1], y = current[2];
            // 到达终点,返回路径得分
            if (x == m - 1 && y == n - 1) {
                return score;
            }
            IntStream.range(0, 4)
                .mapToObj(i -> new int[]{x + DIRECTIONS[i][0], y + DIRECTIONS[i][1]})
                .filter(p -> p[0] >= 0 && p[0] < m && p[1] >= 0 && p[1] < n && !visited[p[0]][p[1]])
                .forEach(p -> {
                    visited[p[0]][p[1]] = true;
                    maxHeap.offer(new int[]{Math.min(score, grid[p[0]][p[1]]), p[0], p[1]});
                });
        }
        return -1; // 不会到达这里,题目保证有路径
    }
}

解释

方法:

这个题解利用了二分查找和深度优先搜索(DFS)的方法来解决问题。首先,定义了一个辅助函数 exist(val),该函数通过DFS检查是否可以从网格的左上角(0,0)移动到右下角(m-1,n-1),同时确保路径上的最小值至少为val。在主函数中,通过二分查找在可能的值域中搜索可以实现这一条件的最大val。这种方法首先设定搜索的范围为两端起点和终点的最小值,然后不断调整搜索范围,直到找到最大的符合条件的val。

时间复杂度:

O(m*n*log(min(grid[0][0], grid[-1][-1])))

空间复杂度:

O(m*n)

代码细节讲解

🦆
在二分查找中,你是如何确定最初的搜索范围 `l` 和 `r` 为0和网格左上角与右下角的最小值?这是否保证了覆盖所有可能的路径值?
在这个问题中,二分查找的目的是寻找可以从网格左上角移动到右下角的路径中,路径上所有点的最小值的最大可能值。选择左上角和右下角的最小值作为初始的`r`值是因为任何成功的路径都必须通过这两个点,因此路径上点的最小值不可能超过这两个点中的最小值。将`l`初始化为0是因为理论上路径点的值可以从0开始。这个范围确保了覆盖所有可能的路径值,因为任何大于右下角和左上角最小值的路径值都不可能构成从起点到终点的有效路径。
🦆
题解中的`exist(val)`函数通过DFS确保路径上的每个点的值不小于`val`。这种方法在找到存在的最小值后是否直接停止搜索,还是会继续探索其他可能的路径?
`exist(val)`函数的设计目的是检查是否存在从左上角到右下角的路径,使得路径上的所有点的值不小于`val`。一旦找到满足条件的路径,函数就会返回True,并停止进一步搜索。如果在整个递归过程中没有找到符合条件的路径,函数最终返回False。因此,函数在确认找到一个有效路径后会立即停止进一步的搜索,不会探索其他可能的路径。
🦆
为什么在二分查找的过程中选择将`mid`更新为`(l + r + 1) // 2`而不是`(l + r) // 2`?这种选择对算法有何影响?
在二分查找中选择`(l + r + 1) // 2`而不是`(l + r) // 2`主要是为了确保在`l`和`r`非常接近时,`mid`能向上取整,避免出现死循环。具体来说,当`l`和`r`相差1时,如果使用`(l + r) // 2`作为`mid`,则`mid`将始终等于`l`,可能导致二分查找陷入无限循环。使用`(l + r + 1) // 2`可以确保`mid`取到`r`,从而使区间向前推进,这对于找到正确答案是必要的。
🦆
在`exist`函数中,如果当前点`(x, y)`已访问,为何还需要再次检查其四个方向的邻接点?这样做是否会导致不必要的重复计算?
在`exist`函数的DFS过程中,对当前点`(x, y)`设置访问标记是为了防止在搜索过程中重复访问同一点,从而避免无限循环。在对一个点进行标记后,只有未被访问的邻接点才会被考虑进一步探索。如果一个邻接点已经被访问,它将不会被再次探索。这种策略减少了重复计算,同时也确保了DFS能够正确地遍历所有可能的路径。每次检查邻接点是否被访问是必要的,因为它决定了是否继续对该方向进行深度搜索。

相关问题