📜  算法测验|须藤放置[1.6] |问题8(1)

📅  最后修改于: 2023-12-03 15:41:09.999000             🧑  作者: Mango

算法测验 | 须藤放置[1.6] | 问题8

这道题目中,我们要求得须藤放置游戏中,从左上角出发到右下角的最短路径长度。

题目描述

须藤放置是一个单人益智类游戏,玩家通过连接数字和对应方向的箭头,完成游戏目标。

游戏界面中,有一个 $n\times n$ 的矩形,每个格子中都填有数字和箭头,其中数字表示通过此格的步数,箭头表示通过此格的方向,箭头的类型可能为上下左右和转角四种类型。

现在,请你编写一个算法,计算从左上角出发到右下角的最短路径长度。

其中,你可以假设每个格子尺寸相等,步数为整数。

样例:

输入:

[
    [0, 2, -1, 1],
    [3, -1, 2, 1],
    [-1, -1, 0, 1],
    [4, 5, 6, 0],
    [2, 3, -1, 7]
]

输出:

12
算法分析

这道题目是一道经典的图论问题,需要使用最短路径算法进行求解。

在这个问题中,我们可以使用广度优先搜索 (BFS) 或 Dijkstra 算法进行求解最短路径。

由于该题中,每个格子中填有数字和箭头,并且各个格子之间通过箭头可以连接,因此可以将问题转化为有向图问题。

对于有向图问题,广度优先搜索常被用来求解最短路径,因为扩展新的状态所消耗的时间是相等的,因此当遇到目标节点时,广度优先搜索一定能够得到最优解。

而对于无权图的单源最短路径问题,BFS 是最优解,而在存在负权边时,Dijkstra 算法是更好的选择。

由于这道题输入中可能存在负权边,因此建议使用 Dijkstra 算法来求解最短路径。

解题思路
  1. 通过邻接表来存储有向图。

  2. 通过优先队列和数组来实现 Dijkstra 算法。

  3. 从起点出发,遍历每一个能够到达的点,并更新到达该点的最短路径。

  4. 将所有已经遍历过的点加入集合中。

  5. 通过遍历到的点,再计算能够到达其他点的最短路径,并更新到数组和队列中。

  6. 循环以上步骤,直到遍历到终点。

CODE:

public class Main {

    /**
     * 使用 Dijkstra 算法求解最短路径
     * @param grid 二维数组存储网格矩阵
     * @return 从左上角出发到右下角的最短路径长度
     */
    public int shortestPath(int[][] grid) {

        int ROW = grid.length, COL = grid[0].length;

        // 邻接表存储有向图
        List<int[]>[] graph = new ArrayList[ROW * COL];
        for (int i = 0; i < ROW * COL; i++) {
            graph[i] = new ArrayList<>();
        }

        // 将二维数组转化为邻接表
        int[] dx = {-1, 0, 1, 0}, dy = {0, -1, 0, 1};
        for (int i = 0; i < ROW; i++) {
            for (int j = 0; j < COL; j++) {
                if (grid[i][j] == -1) {
                    continue;
                }
                int idx = i * COL + j;
                for (int k = 0; k < 4; k++) {
                    int ni = i + dx[k], nj = j + dy[k];
                    if (ni >= 0 && ni < ROW && nj >= 0 && nj < COL && grid[ni][nj] != -1) {
                        int nidx = ni * COL + nj;
                        int weight = grid[i][j];
                        if (k != grid[i][j + 2]) {
                            weight += 1;
                        }
                        graph[idx].add(new int[]{nidx, weight});
                    }
                }
            }
        }

        // 从起点开始遍历
        int start = 0, end = ROW * COL - 1;
        int[] distance = new int[ROW * COL];
        Arrays.fill(distance, Integer.MAX_VALUE);
        distance[start] = 0;

        // 优先队列
        PriorityQueue<int[]> queue = new PriorityQueue<>(Comparator.comparingInt(a -> a[1]));
        queue.offer(new int[]{start, 0});
        Set<Integer> visited = new HashSet<>();

        while (!queue.isEmpty()) {
            int[] curr = queue.poll();
            int idx = curr[0], dist = curr[1];
            if (visited.contains(idx)) {
                continue;
            }
            visited.add(idx);
            if (idx == end) {
                break;
            }

            // 更新能够到达的点的最短距离
            for (int[] neighbor : graph[idx]) {
                int nidx = neighbor[0], weight = neighbor[1];
                if (!visited.contains(nidx) && dist + weight < distance[nidx]) {
                    distance[nidx] = dist + weight;
                    queue.offer(new int[]{nidx, distance[nidx]});
                }
            }
        }

        if (distance[end] == Integer.MAX_VALUE) {
            return -1;
        }
        return distance[end];
    }

}