华为OD机试真题 - 路口最短时间问题 - Dijkstra算法(Python/JS/C/C++ 2024 E卷 200分)

在这里插入图片描述

华为OD机试 2024E卷题库疯狂收录中,刷题点这里

专栏导读

本专栏收录于《华为OD机试真题(Python/JS/C/C++)》

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。

一、题目描述

假定街道是棋盘型的,每格距离相等,车辆通过每格街道需要时间均为 timePerRoad;街道的街口(交叉点)有 交通灯,灯的周期 ```T(=lights[row][col]) ``各不相同;车辆可直行、左转和右转,其中直行和左转需要等相应 T 时间的交通灯才可通行,右转无需等待。现给出 n*m 个街口的交通灯周期,以及起止街口的坐标,计算车辆经过两个街口的最短时间。

其中:

起点和终点的交通灯不计入时间,且可以在任意方向经过街口

不可超出 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:

1、输入

4 4
2 2 3 4
4 3 2 1
1 2 3 4
4 3 2 1
100
3 0
0 3

2、输出

604

测试用例2:

1、输入

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

2、输出

151

3、说明

从 (0, 0) 出发,车辆可以向右或向下移动。

无论车辆选择哪条路径(通过 (0,1) 或 (1,0)),都必须等待交通灯,并在抵达终点之前经历多个等待周期。

最短路径通过Dijkstra算法计算,总通行时间为245。

五、解题思路

这道题目可以抽象为一个带权图的最短路径问题,其中节点代表街道的交叉口,边的权重代表车辆从一个交叉口到另一个交叉口所需的时间。为了求解从起点到终点的最短通行时间,我们可以使用Dijkstra算法,该算法能够在带权图中找到最短路径。

1、Dijkstra算法

Dijkstra算法是一种用于求解加权有向图中单源最短路径问题的经典算法。换句话说,Dijkstra算法能够计算从图中的某个起点(源节点)到图中所有其他节点的最短路径。

Dijkstra算法的基本思想是贪心策略(Greedy Strategy)。算法的核心在于每一步都选择当前能够达到的最短路径,并通过这种方式逐步扩展到整个图。

选择Dijkstra算法的原因在于它能够有效处理带权有向图中的单源最短路径问题,特别适用于所有边权为非负的情况。针对本题的街道网格模型,Dijkstra算法能够有效地处理交通灯的动态等待时间,并快速计算出从起点到终点的最短路径。这种算法的贪心策略和优先队列的结合使得它在处理这种带有复杂时间权重的路径问题时表现得尤为高效。

2、算法步骤

(1)初始化:

将起点的距离设为0,即 dist[start] = 0。

将所有其他节点的距离初始化为无穷大(表示暂时不可达)。

使用一个优先队列(或最小堆)来存储当前已知的最短路径距离,从而可以快速找到下一个最短路径节点。

(2)贪心选择:

从优先队列中取出距离最小的节点,将其标记为已访问,并更新它的邻接节点的距离。

对于当前节点的每一个邻接节点,计算从起点通过当前节点到达这个邻接节点的路径长度。如果这条路径比之前记录的到达该邻接节点的路径更短,则更新该邻接节点的最短距离,并将其加入优先队列中。

(3)重复过程:

重复上述过程,直到优先队列为空,所有节点都已被访问。

(4)终止条件:

当所有节点的最短路径都已确定,或者优先队列为空时,算法终止。

3、步骤详解

  1. 输入建模:
    • 输入提供了一个 n*m 的矩阵,表示街道上的交通灯周期。车辆从一个交叉口移动到相邻的交叉口需要一个固定的时间 timePerRoad,但如果车辆是直行或左转,还需要等待交通灯变绿才能通行。
    • 起点和终点的交通灯不计入总时间,因此计算路径时可以直接开始和结束于这些点。
  2. 方向处理:
    • 车辆可以向上、下、左、右四个方向行驶。我们需要分别处理这四个方向,因为不同的方向会影响通行时间(直行和左转可能需要等待,右转则不需要)。
    • 使用一个四维数组 minTimeToReach 记录车辆从起点到达每个交叉口的最短时间,其中每一维度对应一个方向。
  3. 优先队列(Priority Queue)与Dijkstra算法:
    • Dijkstra算法利用优先队列来选择到达时间最短的节点(交叉口)进行处理。
    • 每次处理一个交叉口时,计算车辆前往四个相邻交叉口的时间。如果新计算出的时间短于已记录的最短时间,则更新并将该交叉口加入优先队列。
  4. 等待时间计算:
    • 对于每一个交叉口,如果车辆是直行或左转,可能需要等待交通灯变绿。等待时间取决于当前车辆到达交叉口的时间与交通灯周期的关系。
    • 具体而言,车辆到达时,如果交通灯处于红灯状态(即时间不满足直行或左转的条件),则需要等待下一次绿灯。
  5. 寻找最短时间:
    • 最终,当我们从起点到达终点时,需要比较四个方向的最短时间,取其中的最小值即为答案。

六、Python算法源码

import heapq

# 定义四个方向的偏移量:上、右、下、左
direction_offsets = [(-1, 0), (0, 1), (1, 0), (0, -1)]

class Intersection:
    def __init__(self, row, col, prev_row, prev_col, time, direction):
        self.row = row
        self.col = col
        self.prev_row = prev_row
        self.prev_col = prev_col
        self.time = time
        self.direction = direction

    def __lt__(self, other):
        return self.time < other.time

def find_shortest_time(lights, time_per_road, start_row, start_col, end_row, end_col):
    num_rows = len(lights)
    num_cols = len(lights[0])

    # min_time_to_reach数组存储到每个点的最短时间,考虑四个方向
    min_time_to_reach = [[[float('inf')] * 4 for _ in range(num_cols)] for _ in range(num_rows)]

    # 优先队列,用于存储和选择下一个访问的节点
    pq = []
    for direction in range(4):
        min_time_to_reach[start_row][start_col][direction] = 0
        heapq.heappush(pq, Intersection(start_row, start_col, -1, -1, 0, direction))

    # 主循环,直到优先队列为空
    while pq:
        current_intersection = heapq.heappop(pq)

        # 遍历四个方向
        for direction in range(4):
            next_row = current_intersection.row + direction_offsets[direction][0]
            next_col = current_intersection.col + direction_offsets[direction][1]

            # 检查新坐标是否在有效范围内
            if next_row < 0 or next_row >= num_rows or next_col < 0 or next_col >= num_cols:
                continue

            # 避免回到上一个节点
            if next_row == current_intersection.prev_row and next_col == current_intersection.prev_col:
                continue

            # 计算新的通行时间
            new_time = current_intersection.time + time_per_road

            # 如果不是右转,则可能需要等待交通灯
            if (current_intersection.direction + 1) % 4 != direction:
                new_time += lights[current_intersection.row][current_intersection.col]

            # 更新最短路径和添加新节点到队列
            if new_time < min_time_to_reach[next_row][next_col][direction]:
                min_time_to_reach[next_row][next_col][direction] = new_time
                heapq.heappush(pq, Intersection(next_row, next_col, current_intersection.row, current_intersection.col, new_time, direction))

    # 从四个方向中找到最短的时间
    shortest_time = min(min_time_to_reach[end_row][end_col])

    return shortest_time

def main():
    # 读取 n 和 m
    num_rows, num_cols = map(int, input().split())

    # 读取 lights 矩阵
    lights = []
    for _ in range(num_rows):
        lights.append(list(map(int, input().split())))

    # 读取 time_per_road
    time_per_road = int(input())

    # 读取起点坐标
    start_row, start_col = map(int, input().split())

    # 读取终点坐标
    end_row, end_col = map(int, input().split())

    # 输出从起点到终点的最短时间
    print(find_shortest_time(lights, time_per_road, start_row, start_col, end_row, end_col))

if __name__ == "__main__":
    main()

七、JavaScript算法源码

class Intersection {
    constructor(row, col, prevRow, prevCol, time, direction) {
        this.row = row;
        this.col = col;
        this.prevRow = prevRow;
        this.prevCol = prevCol;
        this.time = time;
        this.direction = direction;
    }
}

function findShortestTime(lights, timePerRoad, startRow, startCol, endRow, endCol) {
    const numRows = lights.length;
    const numCols = lights[0].length;

    // minTimeToReach数组存储到每个点的最短时间,考虑四个方向
    const minTimeToReach = Array.from({ length: numRows }, () =>
        Array.from({ length: numCols }, () => Array(4).fill(Infinity))
    );

    // 优先队列,用于存储和选择下一个访问的节点
    const pq = [];
    for (let direction = 0; direction < 4; direction++) {
        minTimeToReach[startRow][startCol][direction] = 0;
        pq.push(new Intersection(startRow, startCol, -1, -1, 0, direction));
    }

    pq.sort((a, b) => a.time - b.time); // 使用简单排序模拟优先队列

    // 主循环,直到优先队列为空
    while (pq.length > 0) {
        const currentIntersection = pq.shift();

        // 遍历四个方向
        for (let direction = 0; direction < 4; direction++) {
            const nextRow = currentIntersection.row + directionOffsets[direction][0];
            const nextCol = currentIntersection.col + directionOffsets[direction][1];

            // 检查新坐标是否在有效范围内
            if (nextRow < 0 || nextRow >= numRows || nextCol < 0 || nextCol >= numCols) continue;

            // 避免回到上一个节点
            if (nextRow === currentIntersection.prevRow && nextCol === currentIntersection.prevCol) continue;

            // 计算新的通行时间
            let newTime = currentIntersection.time + timePerRoad;

            // 如果不是右转,则可能需要等待交通灯
            if ((currentIntersection.direction + 1) % 4 !== direction) {
                newTime += lights[currentIntersection.row][currentIntersection.col];
            }

            // 更新最短路径和添加新节点到队列
            if (newTime < minTimeToReach[nextRow][nextCol][direction]) {
                minTimeToReach[nextRow][nextCol][direction] = newTime;
                pq.push(new Intersection(nextRow, nextCol, currentIntersection.row, currentIntersection.col, newTime, direction));
                pq.sort((a, b) => a.time - b.time); // 重新排序
            }
        }
    }

    // 从四个方向中找到最短的时间
    let shortestTime = Infinity;
    for (let direction = 0; direction < 4; direction++) {
        shortestTime = Math.min(shortestTime, minTimeToReach[endRow][endCol][direction]);
    }

    return shortestTime;
}

function main() {
    // 读取输入
    const [numRows, numCols] = readline().split(' ').map(Number);

    // 读取 lights 矩阵
    const lights = [];
    for (let i = 0; i < numRows; i++) {
        lights.push(readline().split(' ').map(Number));
    }

    // 读取 timePerRoad
    const timePerRoad = Number(readline());

    // 读取起点坐标
    const [startRow, startCol] = readline().split(' ').map(Number);

    // 读取终点坐标
    const [endRow, endCol] = readline().split(' ').map(Number);

    // 输出从起点到终点的最短时间
    console.log(findShortestTime(lights, timePerRoad, startRow, startCol, endRow, endCol));
}

// 定义四个方向的偏移量:上、右、下、左
const directionOffsets = [
    [-1, 0], // 上
    [0, 1],  // 右
    [1, 0],  // 下
    [0, -1]  // 左
];

// 调用 main 函数
main();

八、C算法源码

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

// 定义四个方向的偏移量:上、右、下、左
int directionOffsets[4][2] = {
    {-1, 0},  // 上
    {0, 1},   // 右
    {1, 0},   // 下
    {0, -1}   // 左
};

// 交叉点结构体,用于优先队列
typedef struct {
    int row, col, prevRow, prevCol, time, direction;
} Intersection;

// 简单优先队列(基于数组实现)
typedef struct {
    Intersection *data;
    int size;
    int capacity;
} PriorityQueue;

// 创建优先队列
PriorityQueue* createPriorityQueue(int capacity) {
    PriorityQueue *pq = (PriorityQueue*)malloc(sizeof(PriorityQueue));
    pq->data = (Intersection*)malloc(sizeof(Intersection) * capacity);
    pq->size = 0;
    pq->capacity = capacity;
    return pq;
}

// 比较两个交叉点的通行时间
int compareIntersections(const Intersection *a, const Intersection *b) {
    return a->time - b->time;
}

// 向优先队列中插入元素
void push(PriorityQueue *pq, Intersection elem) {
    if (pq->size >= pq->capacity) return;
    pq->data[pq->size++] = elem;

    // 上浮调整
    int i = pq->size - 1;
    while (i > 0) {
        int parent = (i - 1) / 2;
        if (compareIntersections(&pq->data[i], &pq->data[parent]) >= 0) break;
        Intersection temp = pq->data[i];
        pq->data[i] = pq->data[parent];
        pq->data[parent] = temp;
        i = parent;
    }
}

// 从优先队列中移除并返回最小元素
Intersection pop(PriorityQueue *pq) {
    Intersection minElem = pq->data[0];
    pq->data[0] = pq->data[--pq->size];

    // 下沉调整
    int i = 0;
    while (1) {
        int left = 2 * i + 1, right = 2 * i + 2, smallest = i;
        if (left < pq->size && compareIntersections(&pq->data[left], &pq->data[smallest]) < 0) {
            smallest = left;
        }
        if (right < pq->size && compareIntersections(&pq->data[right], &pq->data[smallest]) < 0) {
            smallest = right;
        }
        if (smallest == i) break;
        Intersection temp = pq->data[i];
        pq->data[i] = pq->data[smallest];
        pq->data[smallest] = temp;
        i = smallest;
    }

    return minElem;
}

// 检查优先队列是否为空
int isEmpty(PriorityQueue *pq) {
    return pq->size == 0;
}

// 释放优先队列
void freePriorityQueue(PriorityQueue *pq) {
    free(pq->data);
    free(pq);
}

// 计算从起点到终点的最短通行时间
int findShortestTime(int **lights, int timePerRoad, int numRows, int numCols, int startRow, int startCol, int endRow, int endCol) {
    // minTimeToReach数组存储到每个点的最短时间,考虑四个方向
    int ***minTimeToReach = (int***)malloc(numRows * sizeof(int**));
    for (int i = 0; i < numRows; i++) {
        minTimeToReach[i] = (int**)malloc(numCols * sizeof(int*));
        for (int j = 0; j < numCols; j++) {
            minTimeToReach[i][j] = (int*)malloc(4 * sizeof(int));
            for (int k = 0; k < 4; k++) {
                minTimeToReach[i][j][k] = INT_MAX;
            }
        }
    }

    // 优先队列,用于存储和选择下一个访问的节点
    PriorityQueue *pq = createPriorityQueue(numRows * numCols * 4);
    for (int direction = 0; direction < 4; direction++) {
        minTimeToReach[startRow][startCol][direction] = 0;
        Intersection initial = {startRow, startCol, -1, -1, 0, direction};
        push(pq, initial);
    }

    // 主循环,直到优先队列为空
    while (!isEmpty(pq)) {
        Intersection currentIntersection = pop(pq);

        // 遍历四个方向
        for (int direction = 0; direction < 4; direction++) {
            int nextRow = currentIntersection.row + directionOffsets[direction][0];
            int nextCol = currentIntersection.col + directionOffsets[direction][1];

            // 检查新坐标是否在有效范围内
            if (nextRow < 0 || nextRow >= numRows || nextCol < 0 || nextCol >= numCols) continue;

            // 避免回到上一个节点
            if (nextRow == currentIntersection.prevRow && nextCol == currentIntersection.prevCol) continue;

            // 计算新的通行时间
            int newTime = currentIntersection.time + timePerRoad;

            // 如果不是右转,则可能需要等待交通灯
            if ((currentIntersection.direction + 1) % 4 != direction) {
                newTime += lights[currentIntersection.row][currentIntersection.col];
            }

            // 更新最短路径和添加新节点到队列
            if (newTime < minTimeToReach[nextRow][nextCol][direction]) {
                minTimeToReach[nextRow][nextCol][direction] = newTime;
                Intersection newIntersection = {nextRow, nextCol, currentIntersection.row, currentIntersection.col, newTime, direction};
                push(pq, newIntersection);
            }
        }
    }

    // 从四个方向中找到最短的时间
    int shortestTime = INT_MAX;
    for (int direction = 0; direction < 4; direction++) {
        if (minTimeToReach[endRow][endCol][direction] < shortestTime) {
            shortestTime = minTimeToReach[endRow][endCol][direction];
        }
    }

    // 释放内存
    for (int i = 0; i < numRows; i++) {
        for (int j = 0; j < numCols; j++) {
            free(minTimeToReach[i][j]);
        }
        free(minTimeToReach[i]);
    }
    free(minTimeToReach);
    freePriorityQueue(pq);

    return shortestTime;
}

int main() {
    int numRows, numCols;

    // 读取 n 和 m
    scanf("%d %d", &numRows, &numCols);

    // 读取 lights 矩阵
    int **lights = (int**)malloc(numRows * sizeof(int*));
    for (int i = 0; i < numRows; i++) {
        lights[i] = (int*)malloc(numCols * sizeof(int));
        for (int j = 0; j < numCols; j++) {
            scanf("%d", &lights[i][j]);
        }
    }

    // 读取 timePerRoad
    int timePerRoad;
    scanf("%d", &timePerRoad);

    // 读取起点坐标
    int startRow, startCol;
    scanf("%d %d", &startRow, &startCol);

    // 读取终点坐标
    int endRow, endCol;
    scanf("%d %d", &endRow, &endCol);

    // 输出从起点到终点的最短时间
    int result = findShortestTime(lights, timePerRoad, numRows, numCols, startRow, startCol, endRow, endCol);
    printf("%d\n", result);

    // 释放内存
    for (int i = 0; i < numRows; i++) {
        free(lights[i]);
    }
    free(lights);

    return 0;
}

九、C++算法源码

#include <iostream>
#include <vector>
#include <queue>
#include <tuple>
#include <limits>

using namespace std;

// 定义四个方向的偏移量:上、右、下、左
int directionOffsets[4][2] = {
    {-1, 0},  // 上
    {0, 1},   // 右
    {1, 0},   // 下
    {0, -1}   // 左
};

// 交叉点结构体,用于优先队列
struct Intersection {
    int row, col, prevRow, prevCol, time, direction;
    // 构造函数
    Intersection(int r, int c, int pr, int pc, int t, int dir)
        : row(r), col(c), prevRow(pr), prevCol(pc), time(t), direction(dir) {}
    // 重载小于运算符,用于优先队列
    bool operator<(const Intersection& other) const {
        return time > other.time; // 注意这里是大于号,因为优先队列是最大堆
    }
};

// 计算从起点到终点的最短通行时间
int findShortestTime(const vector<vector<int>>& lights, int timePerRoad, int startRow, int startCol, int endRow, int endCol) {
    int numRows = lights.size();
    int numCols = lights[0].size();

    // minTimeToReach数组存储到每个点的最短时间,考虑四个方向
    vector<vector<vector<int>>> minTimeToReach(numRows, vector<vector<int>>(numCols, vector<int>(4, numeric_limits<int>::max())));

    // 优先队列,用于存储和选择下一个访问的节点
    priority_queue<Intersection> pq;
    for (int direction = 0; direction < 4; direction++) {
        minTimeToReach[startRow][startCol][direction] = 0;
        pq.emplace(startRow, startCol, -1, -1, 0, direction);
    }

    // 主循环,直到优先队列为空
    while (!pq.empty()) {
        Intersection currentIntersection = pq.top();
        pq.pop();

        // 遍历四个方向
        for (int direction = 0; direction < 4; direction++) {
            int nextRow = currentIntersection.row + directionOffsets[direction][0];
            int nextCol = currentIntersection.col + directionOffsets[direction][1];

            // 检查新坐标是否在有效范围内
            if (nextRow < 0 || nextRow >= numRows || nextCol < 0 || nextCol >= numCols) continue;

            // 避免回到上一个节点
            if (nextRow == currentIntersection.prevRow && nextCol == currentIntersection.prevCol) continue;

            // 计算新的通行时间
            int newTime = currentIntersection.time + timePerRoad;

            // 如果不是右转,则可能需要等待交通灯
            if ((currentIntersection.direction + 1) % 4 != direction) {
                newTime += lights[currentIntersection.row][currentIntersection.col];
            }

            // 更新最短路径和添加新节点到队列
            if (newTime < minTimeToReach[nextRow][nextCol][direction]) {
                minTimeToReach[nextRow][nextCol][direction] = newTime;
                pq.emplace(nextRow, nextCol, currentIntersection.row, currentIntersection.col, newTime, direction);
            }
        }
    }

    // 从四个方向中找到最短的时间
    int shortestTime = numeric_limits<int>::max();
    for (int direction = 0; direction < 4; direction++) {
        shortestTime = min(shortestTime, minTimeToReach[endRow][endCol][direction]);
    }

    return shortestTime;
}

int main() {
    int numRows, numCols;

    // 读取 n 和 m
    cin >> numRows >> numCols;

    // 读取 lights 矩阵
    vector<vector<int>> lights(numRows, vector<int>(numCols));
    for (int i = 0; i < numRows; i++) {
        for (int j = 0; j < numCols; j++) {
            cin >> lights[i][j];
        }
    }

    // 读取 timePerRoad
    int timePerRoad;
    cin >> timePerRoad;

    // 读取起点坐标
    int startRow, startCol;
    cin >> startRow >> startCol;

    // 读取终点坐标
    int endRow, endCol;
    cin >> endRow >> endCol;

    // 输出从起点到终点的最短时间
    cout << findShortestTime(lights, timePerRoad, startRow, startCol, endRow, endCol) << endl;

    return 0;
}


🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 2024 E卷 200分)

🏆本文收录于,华为OD机试真题(Python/JS/C/C++)

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哪 吒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值