矩阵中的最短路问题

题目见 2290. 到达角落需要移除障碍物的最小数目

类似题目:1368. 使网格图至少有一条有效路径的最小代价

题目大意:给定一个m * n的矩阵,每个点都有对应意义的权值,求从起点到终点的最短距离(权值路径)。

以2290题为例,给定m * n的矩阵grid,每个单元格可能有两个值:0表示无障碍物,1表示有障碍物,可以上下左右移动,求从(0,0)到(m - 1, n - 1)需要移除障碍物的最小数目。

我们可以把矩阵中的每个点看作图结构中的某个结点,从该结点到相邻结点需要移除障碍物的个数为边的权值,这样求出从起点到终点的最短路即为所求答案。

解法一:Dijkstra算法

根据Dijkstra的思想,①每次从未标记的结点中选取距离出发点最近的结点,标记并加入最优集合中,②计算刚加入的点A到其相邻结点B的距离,如果dis[A] + grid[A —B]  < dis[B],则更新dis[B]。

上述算法的时间复杂度为O(V^2 + E),V代表结点集大小,即m * n,E代表边集大小,可用优先队列进行优化:

使用优先队列存储结点以及起点到该结点的最短路长度,即可每次从结点中选取出离出发点最近的结点,若该结点被标记过,删除该结点并重新选取。此时复杂度为O((V + E) * logV)。

Java代码如下:

int[][] d = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
public int minimumObstacles(int[][] grid) {
    int m = grid.length, n = grid[0].length;
    boolean[][] isvis = new boolean[m][n];
    //dis[i][j]表示起点到点(i, j)的最短路长度,dis[m - 1][n - 1]即为答案
    int[][] dis = new int[m][n];
    for(int i = 0; i < m; i++){
        dis[i] = Integer.MAX_VALUE;
    }
    dis[0][0] = 0;
    //创建优先队列pq,用于保存最优点结合,根据最短路的极小值排序
    PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[2] - b[2]);
    pq.offer(new int[]{0, 0, 0});
    while(!pq.isEmpty()){
        //取出当前距离起点最近的点cur,如果已标记则continue
        int[] cur = pq.poll();
        int x = cur[0], y = cur[1];
        if(isvis[x][y]){
            continue;
        }
        //更新相邻结点的最短路
        for(int i = 0; i <4; i++){
            int nx = x + d[i][0], ny = y + d[i][1];
            if(nx < 0 || nx >= m || ny < 0 || ny >= n){
                continue;
            }
            int newdis = cur[2] + grid[nx][ny];
            if(newdis < dis[nx][ny]) {
                dis[nx][ny] = newdis;
                pq.offer(new int[]{nx, ny});
            }
        }
    }
    return dis[m - 1][n - 1];
}

解法二:0-1 BFS

BFS也能解决上述的最短路问题,即从起点逐层遍历,直到遍历到最终结点。但BFS成功的前提是,所有边的权值均为1,然而当前出现了边的权值为0的情况,此时需要对算法进行修改,使得保证每次遍历时,当前结点到起点的距离大于等于上一次遍历的结点到起点的距离,即保证BFS逐层扩散的正确性。

修改方法:使用双端队列。如果当前结点到某相邻结点的距离为0,则加入队首,否则加入队尾。这样就保证了每次选取的结点必定为当前距离起点最近的结点,因为每次都是从队首取的。

Golang代码:

func minimumObstacles(grid [][]int) int {
    m, n := len(grid), len(grid[0])
    dx := []int{0, 0, -1, 1}
    dy := []int{-1, 1, 0, 0}
    path := make([][]int, m)
    for i := range path {
        path[i] = make([]int, n)
        for j := range path[i] {
            path[i][j] = math.MaxInt32
        }
    }
    path[0][0] = 0
    //使用两个切片拼接模拟双端队列
    q := [2][][]int{{{0, 0}}}
    for len(q[0]) > 0 || len(q[1]) > 0 {
        var cur []int
        if len(q[0]) > 0 {
            cur = q[0][len(q[0] - 1)]
            q[0] = q[0][:len(q[0] - 1)]
        }else {
            cur = q[1][0]
            q[1] = q[1][1:]
        }
        x, y := cur[0], cur[1]
        for i := 0; i < 4; i++ {
            nx, ny := x + dx[i], y + dy[i]
            if nx < 0 || nx >= m || ny < 0 || ny >= n {
                continue
            }
            newdis := path[x][y] + grid[nx][ny]
            if newdis < path[x][y] {
                path[nx][ny] = newdis
                if grid[nx][ny] == 1 {
                    q[1] = append(q[1], []int{nx,ny})
                }else{
                    q[0] = append(q[0], []int{nx,ny})
                }
            }
        }
    }
    return path[m - 1][n - 1]
}
posted @ 2022-06-02 22:08  夜满星河  阅读(365)  评论(0)    收藏  举报