华为od(D卷)路口最短时间问题

题目描述

假定街道是棋盘型的,每格距离相等,车辆通过每格街道需要时间均为 timePerRoad;

街道的街口(交叉点)有交通灯,灯的周期 T(=lights[row][col])各不相同;

车辆可直行、左转和右转,其中直行和左转需要等相应 T 时间的交通灯才可通行,右转无需等待。

现给出 n * m 个街口的交通灯周期,以及起止街口的坐标,计算车辆经过两个街口的最短时间。

其中:

  1. 起点和终点的交通灯不计入时间,且可以在任意方向经过街口
  2. 不可超出 n * m 个街口,不可跳跃,但边线也是道路(即:lights[0][0] -> lights[0][1] 是有效路径)

入口函数定义:

/**
 * @param lights:n*m个街口每个交通灯的周期,值范围[0, 120],n和m的范围为[1,9]
 * @param timePreRoad:相邻两个街口之间街道的通行时间,范围为[0,600]
 * @param rowStart:起点的行号
 * @param colStart:起点的列号
 * @param rowEnd:终点的行号
 * @param colEnd:终点的列号
 * @return lights[rowStart][colStart] 与 lights[rowEnd][colEnd] 两个街口之间的最短通行时间
 */
int calcTime(int[][] lights, int timePreRoad, int rowStart, int colStart, int rowEnd, int colEnd)

输入描述

第一行输入 n 和 m,以空格分隔

之后 n 行输入 lights矩阵,矩阵每行m个整数,以空格分隔

之后一行输入 timePerRoad

之后一行输入 rowStart colStart,以空格分隔

最后一行输入 rowEnd colEnd,以空格分隔

输出描述

lights[rowStart][colStart] 与 lights[rowEnd][colEnd] 两个街口之间的最短通行时间

示例1

输入
3 3
1 2 3
4 5 6
7 8 9
60
0 0
2 2

输出
245

说明
行走路线为 (0,0) -> (0,1) -> (1,1) -> (1,2) -> (2,2) 走了4格路,2个右转,1个左转,共耗时 60+0+60+5+60+0+60=245

思路

  1. 暴力解法回溯出所有路线,计算距离

  2. 在有权图(权值非负数)中求从起点到其他节点的最短路径算法: dijkstra 算法(dijkstra 算法可以同时求 起点到所有节点的最短路径)

dijkstra:

  • 第一步,选源点到哪个节点近且该节点未被访问过
  • 第二步,该最近节点被标记访问过
  • 第三步,更新非访问节点到源点的距离(即更新minDist数组)

注意:到达当前节点的最短距离,不一定能推导出下一个节点的最小距离

例如: 到达(1,2)的距离(0,0) -> (0,1) -> (0,2) ->(1,2) : 60*3+2+0 = 182;比(0,0) -> (0,1) -> (1,1) -> (1,2) 60*3+5+0 = 185; 182的线路小,但是这个线路不能推导出(2,2)的最优解, 所以minDist在存储时不能只存储距离,还要存储方向信息。

方向编号处理: 顺时针规定方向编号,从0开始
路口编号处理: 按照先行后列设置编号,从0开始(目的为了降低数组维度,降低for循环嵌套)

代码


public class Demo20 {
    // 方向顺序
    private static final int[][] dir = new int[][] {
            {-1, 0},
            {0, 1},
            {1, 0},
            {0, -1},

    };


    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (in.hasNextInt()) {
            int rows = in.nextInt();
            int cols = in.nextInt();
            int[][] lights = new int[rows][cols];
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    lights[i][j] = in.nextInt();
                }
            }
            int timePerRoad = in.nextInt();
            int rowStart = in.nextInt();
            int colStart = in.nextInt();
            int rowEnd = in.nextInt();
            int colEnd = in.nextInt();
            System.out.println(calcTime(lights, timePerRoad, rowStart, colStart, rowEnd, colEnd));
        }
        in.close();


    }

    /**
     * @param lights:n*m个街口每个交通灯的周期,值范围[0,           120],n和m的范围为[1,9]
     * @param timePreRoad:相邻两个街口之间街道的通行时间,范围为[0,600]
     * @param rowStart:起点的行号
     * @param colStart:起点的列号
     * @param rowEnd:终点的行号
     * @param colEnd:终点的列号
     * @return lights[rowStart][colStart] 与 lights[rowEnd][colEnd] 两个街口之间的最短通行时间
     */
    static int calcTime(int[][] lights, int timePreRoad, int rowStart, int colStart, int rowEnd, int colEnd) {
        int startNum = toNumber(rowStart, colStart, lights);
        int count = lights.length * lights.length;
        //非访问节点到源点的时间
        int[][] minDist = new int[count + 1][4];
        for (int[] ints : minDist) {
            Arrays.fill(ints, Integer.MAX_VALUE);
        }

        minDist[startNum][0] = 0;
        minDist[startNum][1] = 0;
        minDist[startNum][2] = 0;
        minDist[startNum][3] = 0;


        // 是否访问
        boolean[][] visited = new boolean[count + 1][4];

        // [isFirst, number, dir, dist]
        PriorityQueue<Integer[]> queue = new PriorityQueue<>(Comparator.comparingInt(t0 -> t0[3]));
        queue.offer(new Integer[] {1, startNum, 0, 0});


        while (!queue.isEmpty()) {
            //- 第一步,选源点到哪个节点近且该节点未被访问过
            Integer[] curNode = queue.poll();
            boolean isFirst = curNode[0] == 1;
            int curNumber = curNode[1];
            int curDir = curNode[2];
            if (visited[curNumber][curDir]) {
                continue;
            }
            //- 第二步,该最近节点被标记访问过
            visited[curNumber][curDir] = true;
            int[] ints = toXY(curNumber, lights);
            int curRow = ints[0];
            int curCol = ints[1];

            //- 第三步,更新非访问节点到源点的距离(即更新minDist数组)
            for (int j = 0; j < dir.length; j++) {
                // 不能去 来的方向
                if (!isFirst && oppositeDir(j) == curDir) {
                    continue;
                }
                int nextX = curRow + dir[j][0];
                int nextY = curCol + dir[j][1];
                if (!legalXY(nextX, nextY, lights)) {
                    continue;
                }


                int nextNumber = toNumber(nextX, nextY, lights);
                if (visited[nextNumber][j]) {
                    continue;
                }
                int wait = 0;
                if (!isFirst) {
                    wait = calcWaitTime(lights, curDir, curRow, curCol, j);
                }
                int needTime = wait + timePreRoad;
                if (needTime + minDist[curNumber][curDir] < minDist[nextNumber][j]) {
                    minDist[nextNumber][j] = needTime + minDist[curNumber][curDir];
                    // [isFirst, number, dir, dist]
                    queue.offer(new Integer[] {0, nextNumber, j, minDist[nextNumber][j]});
                }

            }

        }

        int endNumber = toNumber(rowEnd, colEnd, lights);
        int dist = Integer.MAX_VALUE;
        for (int i = 0; i < 4; i++) {
            int d = minDist[endNumber][i];
            if (d < dist) {
                dist = d;
            }
        }
        return dist;
    }

    // 结算等待时间
    public static int calcWaitTime(int[][] lights, int dirFlag, int x, int y, int nextDirFlag) {
        // 右转判断
        if ((nextDirFlag + 1) % 4 == dirFlag) {
            return 0;
        } else {
            return lights[x][y];
        }
    }

    // 反方向计算
    public static int oppositeDir(int dir) {
        return (dir + 2) % 4;
    }


    // 坐标转编号
    public static int toNumber(int x, int y, int[][] lights) {
        return x * lights.length + y;
    }

    // 编号转坐标
    public static int[] toXY(int number, int[][] lights) {
        int curCol = number % lights.length;
        int curRow = number / lights.length;
        return new int[] {curRow, curCol};
    }

    // 合法坐标
    public static boolean legalXY(int x, int y, int[][] lights) {
        return x >= 0 && x < lights.length && y >= 0 && y < lights[0].length;

    }


}




  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值