华为OD机试(C卷+D卷)2024真题目录(Java & c++ & python)
题目描述
假定街道是棋盘型的,每格距离相等,车辆通过每格街道需要时间均为 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] 两个街口之间的最短通行时间
用例
输入
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
示例解析:
(0,0) -> (0,1) 起点的交通灯不计入时间,因此通过该路口耗时60
(0,1) -> (1,1) 右转,右转不等待,因此通过该路口耗时60
(1,1) -> (1,2) 左转,左转需要等待lights[1][1]=5,因此通过该路口耗时60+5
(1,2) -> (2,2) 右转,右转不等待,因此通过该路口耗时60
解题思路
本题虽然地图矩阵数量级很小,n和m的范围为[1,9],但是本题会存在回路的情况,比如用例:
4 3
68 61 39
39 56 42
104 30 88
82 58 4
0
3 0
1 0
这个用例的答案是60,路径如下图所示:
因此本题不能使用暴力搜索找所有路径,因为肯定会超时。
如果将本题的地图矩阵看成是有权图的话,则我们会发现,每两个位置(当前位置、下一个位置)的连线(边)的权重是不固定,会随着第三个位置(上一个位置)的改变而改变。
因此,我们无法使用常规的最短路问题思路来解决此题。
本题地图矩阵的每个位置,都可能被来源于四个方向的路径探索到,即:
- 从当前位置上方来的路径
- 从当前位置下方来的路径
- 从当前位置左方来的路径
- 从当前位置右方来的路径
比如下图所示:
因此对于每一个位置而言,其实有四个方向维度,我们需要为每一个位置定义一个长度为4的数组,用于记录,不同来源方向的最短路径权重。
假设地图矩阵 n 行,m列,那么我们应该定义一个最短路表 dist = new int[n][m][4]
dist[i][j][k]:表示以来源方向k到达位置(i, j)的最短路权重
这样的话,我们就可以解决动态边权问题了,因为所谓的动态边权,其实就是四个来源方向对应的四种边权,并非真正的动态。
之后,我们就可以按照正常的最短路问题来解决此题,这里可以使用Dijkstra算法求解两点之间的最短路。
Dijkstra算法相关知识
本题还有一个难点就是判断拐弯方向,这里建议采用向量叉积的知识来判断三点形成的拐弯方向。
本题还有一点需要注意的是:
本题只允许直行、左拐、右拐,不能够调头,即下面路径是不允许的
C++、Java、Python代码如下:
C++参考代码
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int calcTime(int **lights, int lightsRowLen, int *lightsColLen, int timePerRoad, int rowStart, int colStart, int rowEnd, int colEnd) {
int n = lightsRowLen;
int m = *lightsColLen;
vector<vector<vector<int>>> dist(n, vector<vector<int>>(m, vector<int>(4, INT_MAX)));
auto cmp = [](const vector<int> &a, const vector<int> &b) {
return a[4] > b[4];
};
priority_queue<vector<int>, vector<vector<int>>, decltype(cmp)> pq(cmp);
for (int k = 0; k < 4; k++) {
dist[rowStart][colStart][k] = 0;
pq.push({-1, -1, rowStart, colStart, 0});
}
int offsets[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
while (!pq.empty()) {
auto tmp = pq.top();
pq.pop();
int preX = tmp[0];
int preY = tmp[1];
int curX = tmp[2];
int curY = tmp[3];
int cost = tmp[4];
for (int k = 0; k < 4; k++) {
int newX = curX + offsets[k][0];
int newY = curY + offsets[k][1];
if (newX < 0 || newX >= n || newY < 0 || newY >= m) continue;
if (newX == preX && newY == preY) continue;
int newCost = cost + timePerRoad;
if (preX != -1 && preY != -1 && getDirection(preX, preY, curX, curY, newX, newY) >= 0) {
newCost += lights[curX][curY];
}
if (newCost >= dist[newX][newY][k]) continue;
dist[newX][newY][k] = newCost;
pq.push({curX, curY, newX, newY, newCost});
}
}
int ans = INT_MAX;
for (int k = 0; k < 4; k++) {
ans = min(ans, dist[rowEnd][colEnd][k]);
}
return ans;
}
int getDirection(int preX, int preY, int curX, int curY, int nextX, int nextY) {
int dx1 = curX - preX;
int dy1 = curY - preY;
int dx2 = nextX - curX;
int dy2 = nextY - curY;
return dx1 * dy2 - dx2 * dy1;
}
};
Java参考代码
import java.util.PriorityQueue;
import java.util.Scanner;
// 本题实际考试为核心代码模式,即只需要写出下面Solution类实现即可
class Solution {
// 定义四个方向的移动向量,分别对应上、右、下、左
static int[][] offsets = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
/**
* 计算从起点到终点的最短时间
* @param lights 每个格子处的红绿灯等待时间
* @param timePerRoad 每走一步的固定时间
* @param rowStart 起点行坐标
* @param colStart 起点列坐标
* @param rowEnd 终点行坐标
* @param colEnd 终点列坐标
* @return 最短时间
*/
int calcTime(int[][] lights, int timePerRoad, int rowStart, int colStart, int rowEnd, int colEnd) {
int n = lights.length; // 行数
int m = lights[0].length; // 列数
// dist[i][j][k] 表示从方向 k 到达位置 (i,j) 的最短时间
int[][][] dist = new int[n][m][4];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 初始化为最大值,表示尚未到达
for (int k = 0; k < 4; k++) {
dist[i][j][k] = Integer.MAX_VALUE;
}
}
}
// 小顶堆,用于存储和优先处理路径,按照到达时间升序排序
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[4] - b[4]);
// 初始化起点的四个方向,时间为 0
for (int k = 0; k < 4; k++) {
dist[rowStart][colStart][k] = 0;
// 起点没有前一个位置,前一个位置设为 (-1, -1)
pq.add(new int[]{-1, -1, rowStart, colStart, 0});
}
// 进行路径搜索
while (pq.size() > 0) {
int[] tmp = pq.poll(); // 取出时间最短的路径
int preX = tmp[0];
int preY = tmp[1];
int curX = tmp[2];
int curY = tmp[3];
int cost = tmp[4];
// 向四个方向探索
for (int k = 0; k < 4; k++) {
int newX = curX + offsets[k][0];
int newY = curY + offsets[k][1];
// 越界检查
if (newX < 0 || newX >= n || newY < 0 || newY >= m) continue;
// 检查是否为掉头行为(即新位置与前一个位置相同)
if (newX == preX && newY == preY) continue;
// 计算新的时间,包含行走时间
int newCost = cost + timePerRoad;
// 检查是否需要等待红绿灯
if (preX != -1 && preY != -1 && getDirection(preX, preY, curX, curY, newX, newY) >= 0) {
newCost += lights[curX][curY];
}
// 如果新的路径时间不优,则跳过
if (newCost >= dist[newX][newY][k]) continue;
// 否则更新更优路径时间
dist[newX][newY][k] = newCost;
pq.add(new int[]{curX, curY, newX, newY, newCost});
}
}
// 从终点的四个方向选取最小时间作为结果
int ans = Integer.MAX_VALUE;
for (int k = 0; k < 4; k++) {
ans = Math.min(ans, dist[rowEnd][colEnd][k]);
}
return ans;
}
/**
* 根据三个点的坐标计算转弯方向
* @param preX 前一个点的横坐标
* @param preY 前一个点的纵坐标
* @param curX 当前点的横坐标
* @param curY 当前点的纵坐标
* @param nextX 下一个点的横坐标
* @param nextY 下一个点的纵坐标
* @return cur到next的转弯方向, >0 表示向左拐, ==0 表示直行(含掉头), <0 表示向右拐
*/
public static int getDirection(int preX, int preY, int curX, int curY, int nextX, int nextY) {
// 计算向量 pre->cur
int dx1 = curX - preX;
int dy1 = curY - preY;
// 计算向量 cur->next
int dx2 = nextX - curX;
int dy2 = nextY - curY;
// 两向量的叉积 >0 表示左转,==0 表示直行,<0 表示右转
return dx1 * dy2 - dx2 * dy1;
}
}
// 输入处理类,实际考试为核心代码模式,输入处理通常不需要实现
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取输入的网格大小
int n = sc.nextInt();
int m = sc.nextInt();
// 读取每个格子的红绿灯等待时间
int[][] lights = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
lights[i][j] = sc.nextInt();
}
}
// 读取每走一步的固定时间
int timePerRoad = sc.nextInt();
// 读取起点和终点的坐标
int rowStart = sc.nextInt();
int colStart = sc.nextInt();
int rowEnd = sc.nextInt();
int colEnd = sc.nextInt();
// 输出计算出的最短时间
System.out.println(
new Solution().calcTime(lights, timePerRoad, rowStart, colStart, rowEnd, colEnd));
}
}
Python参考代码
import sys
# 根据三点坐标,确定拐弯方向
def getDirection(preX, preY, curX, curY, nextX, nextY):
"""
:param preX: 前一个点横坐标
:param preY: 前一个点纵坐标
:param curX: 当前点横坐标
:param curY: 当前点纵坐标
:param nextX: 下一个点横坐标
:param nextY: 下一个点纵坐标
:return: cur到next的拐弯方向, >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
"""
# 向量 pre->cur
dx1 = curX - preX
dy1 = curY - preY
# 向量 cur->next
dx2 = nextX - curX
dy2 = nextY - curY
# 两个向量的叉积 >0 表示向左拐, ==0 表示直行(含调头), <0 表示向右拐
return dx1 * dy2 - dx2 * dy1
class Solution:
def calcTime(self, lights, timePerRoad, rowStart, colStart, rowEnd, colEnd):
n = len(lights)
m = len(lights[0])
# 到达位置(i,j)的路径有四个来源方向
# dist[i][j][k] 表示从来源方向k到达位置(i,j)所需要的时间,初始化为最大整数值
dist = [[[sys.maxsize] * 4 for _ in range(m)] for _ in range(n)]
# 小顶堆,堆中元素是数组 [前一个位置行号,前一个位置列号,当前位置行号,当前位置列号,到达当前位置需要的时间]
# 到达当前位置的时间越小,优先级越高
pq = []
# 四个来源方向到达出发点位置 (rowStart, colStart) 所需时间均为 0
for k in range(4):
dist[rowStart][colStart][k] = 0
# 出发点位置没有前一个位置,因此前一个位置设为(-1,-1)
pq.append((-1, -1, rowStart, colStart, 0))
# 四个方向的位移 (上, 下, 左, 右)
offsets = ((-1, 0), (1, 0), (0, -1), (0, 1))
# 每次取出最短路
while len(pq) > 0:
# 将堆中的元素按时间升序排序
pq.sort(key=lambda x: -x[4])
preX, preY, curX, curY, cost = pq.pop()
# 向四个方向探索
for k in range(4):
# 新位置
newX = curX + offsets[k][0]
newY = curY + offsets[k][1]
# 新位置越界,则不可进入
if newX < 0 or newX >= n or newY < 0 or newY >= m:
continue
# 本题不允许掉头,因此新位置处于掉头位置的话,不可进入
if newX == preX and newY == preY:
continue
# 每走一步都要花费 timePerRoad 单位时间
newCost = cost + timePerRoad
# 出发的第一步,或者右拐,不需要等待红绿灯,其他情况需要等待红绿灯 lights[curX][curY] 单位时间
if preX != -1 and preY != -1 and getDirection(preX, preY, curX, curY, newX, newY) >= 0:
newCost += lights[curX][curY]
# 如果以来源方向k到达位置(newX, newY)花费的时间 newCost 并非更优,则终止对应路径探索
if newCost >= dist[newX][newY][k]:
continue
# 否则更新为更优时间
dist[newX][newY][k] = newCost
# 并继续探索该路径
pq.append((curX, curY, newX, newY, newCost))
# 最终取(rowEnd, colEnd)终点位置的四个来源方向路径中最短时间的作为题解
return min(dist[rowEnd][colEnd])
# 实际考试时,本题为核心代码模式,即无需我们解析输入输出,因此只需要写出上面代码即可
if __name__ == '__main__':
n, m = map(int, input().split())
lights = [list(map(int, input().split())) for _ in range(n)]
timePerRoad = int(input())
rowStart, colStart = map(int, input().split())
rowEnd, colEnd = map(int, input().split())
print(Solution().calcTime(lights, timePerRoad, rowStart, colStart, rowEnd, colEnd))