华为OD机试 2024E卷题库疯狂收录中,刷题点这里
专栏导读
本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。
一、题目描述
唐僧师徒四人去西天取经,一路翻山越岭。一天,师徒四人途径一个 m×n 长方形区域,已知:
- 将取经队伍作为一个整体,4人行走相同路线。
- 取经队伍的起点为该长方形区域的左上角,目的地为该长方形区域的右下角。
- 行走路线可以向前、后、左、右四个方向前进(不允许直线上下向对角线移动)。
- 输入包含该区域的 m 行 n 列的整数 h,前后移动允许的高度差为 t 表示。
- 要求该区域内的取经队伍移动的高度差在高度 t 的限制内,取经队伍最多有 3 次爆发机会,每使用一次爆发机会,可以让取经队伍任意一次移动突破高度差限制。
请编写程序,队伍通过该区域的移动次数是多少次回合,表示师徒四人无法直接通过该区域。
二、输入描述
输入第一行有三个整数,分别对应为长方形场地的两条边长,和前后移动允许的高度差。三个整数之间以空格分割。后面是 m 行,n 列的整数矩阵 h,表示长方形场地各点的高度。数据量 m ≤ 200,n ≤ 200,t ≤ 20。
每个 h 的高度 h 满足 20 ≤ h ≤ 4000,0 ≤ i ≤ m,0 ≤ j ≤ n。
三、输出描述
一个 整数 表示队伍通过该区域最少的移动次数。
四、测试用例
测试用例1:
1、输入
4 4 10
10 20 30 40
100 120 140 160
200 230 260 290
300 400 500 600
2、输出
6
3、说明
树苗分别种植在位置 (0,0)=10、(0,3)=40、(1,3)=160、(2,3)=290、(3,3)=600,使用了3次爆发机会,移动次数为6次。
测试用例2:
1、输入
1 10 1
11 12 200 14 15 16 317 18 19 20
2、输出
-1
3、说明
在一行10个坑位的位置中,爆发机会最多使用3次,但无法从起点 (0,0)=11 移动到终点 (0,9)=20,因为需要使用4次爆发机会,超过了限制。因此,输出 -1。
测试用例3:
1、输入
2 2 3
1 4
5 8
2、输出
2
3、说明
起点 (0,0)=1。
移动路径:
(0,0)=1 -> (1,0)=5:diff=4 >3,使用爆发1。
(1,0)=5 -> (1,1)=8:diff=3 ≤3,不用爆发。
总移动次数:2。
使用了1次爆发机会。
五、解题思路
1、广度优先搜索BFS
本题涉及在一个二维网格中从起点移动到终点,同时需要考虑移动过程中的高度差限制和爆发机会。
- 广度优先搜索(BFS):由于要求找到最少的移动次数,BFS 是一个合适的选择,因为它天然适用于寻找最短路径的问题。
- 状态表示:每个状态由 (i, j, k) 组成,其中 (i, j) 表示当前位置,k 表示已使用的爆发机会次数(0 <= k <= 3)。
- 访问标记:为了避免重复访问同一状态,使用一个三维布尔数组 visited[m][n][4] 来标记是否已经访问过 (i, j, k) 状态。
- 移动逻辑:
- 从当前格子向四个方向移动。
- 计算高度差,根据高度差决定是否需要使用爆发机会。
- 如果需要使用爆发机会且 k < 3,则可以移动并将 k 加一。
- 将新的状态加入队列中进行下一轮的搜索。
2、关键点
- 爆发机会的管理:爆发机会是有限的资源,需要在必要时使用。
- 状态管理:不仅要记录位置,还要记录已使用的爆发机会次数,以确保不同的使用方式不会互相影响。
- 边界条件:
- 起点即为终点时,移动次数为 0。
- 需要种植的树苗数量大于坑位数量时,输出 -1。
- 处理单行或单列的网格。
3、数据结构的选择
队列(Queue):用于实现 BFS,保证按照层级顺序进行搜索。
三维布尔数组:高效地记录和查询是否访问过特定的状态,避免重复计算。
二维数组:存储网格的高度值,便于快速访问和计算。
六、Python算法源码
# 导入所需的模块
import sys
from collections import deque
def main():
# 读取所有输入并按行分割
input_lines = sys.stdin.read().splitlines()
# 如果没有输入,直接输出-1并返回
if not input_lines:
print(-1)
return
# 读取第一行,包含m, n, t
first_line = input_lines[0].strip().split()
if len(first_line) != 3:
print(-1)
return
m = int(first_line[0]) # 行数
n = int(first_line[1]) # 列数
t = int(first_line[2]) # 允许的高度差
# 检查m和n是否有效
if m <= 0 or n <= 0:
print(-1)
return
# 检查是否有足够的行输入
if len(input_lines) < m + 1:
print(-1)
return
# 初始化高度矩阵
h = []
for i in range(1, m + 1):
# 读取每行的高度值
line = input_lines[i].strip().split()
if len(line) != n:
print(-1)
return
row = []
for val in line:
height = int(val)
if height < 20 or height > 4000:
print(-1)
return
row.append(height)
h.append(row)
# 读取需要种植的树苗数量K
if len(input_lines) < m + 2:
print(-1)
return
K_line = input_lines[m + 1].strip()
if not K_line.isdigit():
print(-1)
return
K = int(K_line)
# 定义最大爆发机会次数
MAX_USED = 3
# 初始化访问标记数组,维度为[m][n][4]
# visited[x][y][k] 表示是否已经访问过位置(x,y)并且已使用k次爆发机会
visited = [[[False for _ in range(MAX_USED + 1)] for _ in range(n)] for _ in range(m)]
# 定义队列并初始化,将起点加入队列
queue = deque()
# 起点坐标为(0,0),已使用爆发机会次数为0
queue.append((0, 0, 0))
visited[0][0][0] = True
# 如果起点即为终点,移动次数为0
if m == 1 and n == 1:
print(0)
return
# 定义四个移动方向:上、下、左、右
directions = [(-1,0), (1,0), (0,-1), (0,1)]
steps = 0 # 移动次数初始化为0
# 开始进行广度优先搜索(BFS)
while queue:
size = len(queue) # 当前层的状态数量
steps += 1 # 每层代表一次移动
# 逐个处理当前层的所有状态
for _ in range(size):
x, y, used = queue.popleft() # 获取当前状态
# 遍历四个可能的移动方向
for dx, dy in directions:
new_x = x + dx # 新的行坐标
new_y = y + dy # 新的列坐标
# 检查新位置是否在网格范围内
if new_x < 0 or new_x >= m or new_y < 0 or new_y >= n:
continue # 越界,跳过
# 计算高度差
height_diff = abs(h[new_x][new_y] - h[x][y])
# 初始化新的爆发机会次数
new_used = used
# 判断是否需要使用爆发机会
if height_diff > t:
new_used += 1 # 使用一次爆发机会
if new_used > MAX_USED:
continue # 爆发机会已用完,无法移动
# 如果新状态未被访问过,则加入队列
if not visited[new_x][new_y][new_used]:
visited[new_x][new_y][new_used] = True # 标记为已访问
queue.append((new_x, new_y, new_used)) # 将新状态加入队列
# 如果到达终点,返回当前的移动次数
if new_x == m - 1 and new_y == n - 1:
print(steps)
return
# 如果无法到达终点,输出-1
print(-1)
# 调用主函数
if __name__ == "__main__":
main()
七、JavaScript算法源码
// 使用 Node.js 的 readline 模块读取标准输入
const readline = require('readline');
// 创建接口实例
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
let inputLines = []; // 存储输入的每一行
// 监听每一行输入
rl.on('line', function(line){
inputLines.push(line.trim()); // 去除首尾空白并存储
}).on('close', function(){
// 如果没有输入,输出-1并退出
if(inputLines.length === 0){
console.log(-1);
return;
}
// 读取第一行,包含m, n, t
const firstLine = inputLines[0].split(/\s+/);
if(firstLine.length !== 3){
console.log(-1);
return;
}
const m = parseInt(firstLine[0]); // 行数
const n = parseInt(firstLine[1]); // 列数
const t = parseInt(firstLine[2]); // 允许的高度差
// 检查m和n是否有效
if(m <= 0 || n <= 0){
console.log(-1);
return;
}
// 检查是否有足够的行输入
if(inputLines.length < m + 1){
console.log(-1);
return;
}
// 初始化高度矩阵
let h = [];
for(let i = 1; i <= m; i++){
const line = inputLines[i].split(/\s+/);
if(line.length !== n){
console.log(-1);
return;
}
let row = [];
for(let j = 0; j < n; j++){
const height = parseInt(line[j]);
if(isNaN(height) || height < 20 || height > 4000){
console.log(-1);
return;
}
row.push(height);
}
h.push(row);
}
// 读取需要种植的树苗数量K
if(inputLines.length < m + 2){
console.log(-1);
return;
}
const K_line = inputLines[m + 1].trim();
const K = parseInt(K_line);
if(isNaN(K)){
console.log(-1);
return;
}
// 定义最大爆发机会次数
const MAX_USED = 3;
// 初始化访问标记数组,维度为[m][n][4]
let visited = [];
for(let i = 0; i < m; i++){
let row = [];
for(let j = 0; j < n; j++){
row.push(new Array(MAX_USED + 1).fill(false));
}
visited.push(row);
}
// 初始化队列并加入起点状态
let queue = [];
// 起点坐标为(0,0),已使用爆发机会次数为0
queue.push({x:0, y:0, used:0});
visited[0][0][0] = true;
// 如果起点即为终点,移动次数为0
if(m === 1 && n === 1){
console.log(0);
return;
}
// 定义四个移动方向:上、下、左、右
const directions = [
[-1, 0], // 上
[1, 0], // 下
[0, -1], // 左
[0, 1] // 右
];
let steps = 0; // 移动次数初始化为0
// 开始进行广度优先搜索(BFS)
while(queue.length > 0){
let size = queue.length; // 当前层的状态数量
steps += 1; // 每层代表一次移动
// 逐个处理当前层的所有状态
for(let i = 0; i < size; i++){
let current = queue.shift(); // 获取当前状态
let {x, y, used} = current;
// 遍历四个可能的移动方向
for(let dir = 0; dir < 4; dir++){
let new_x = x + directions[dir][0]; // 新的行坐标
let new_y = y + directions[dir][1]; // 新的列坐标
// 检查新位置是否在网格范围内
if(new_x < 0 || new_x >= m || new_y < 0 || new_y >= n){
continue; // 越界,跳过
}
// 计算高度差
let height_diff = Math.abs(h[new_x][new_y] - h[x][y]);
// 初始化新的爆发机会次数
let new_used = used;
// 判断是否需要使用爆发机会
if(height_diff > t){
new_used += 1; // 使用一次爆发机会
if(new_used > MAX_USED){
continue; // 爆发机会已用完,无法移动
}
}
// 如果新状态未被访问过,则加入队列
if(!visited[new_x][new_y][new_used]){
visited[new_x][new_y][new_used] = true; // 标记为已访问
queue.push({x: new_x, y: new_y, used: new_used}); // 将新状态加入队列
// 如果到达终点,返回当前的移动次数
if(new_x === m -1 && new_y === n -1){
console.log(steps);
return;
}
}
}
}
}
// 如果无法到达终点,输出-1
console.log(-1);
});
八、C算法源码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 定义最大行数和列数
#define MAX_M 200
#define MAX_N 200
// 定义最大爆发机会次数
#define MAX_USED 3
// 定义一个结构体来表示队伍的状态
typedef struct {
int x; // 当前行坐标
int y; // 当前列坐标
int used; // 已使用的爆发机会次数
} State;
// 定义一个队列节点结构体
typedef struct QueueNode {
State state; // 状态
struct QueueNode* next; // 下一个节点
} QueueNode;
// 定义一个队列结构体
typedef struct {
QueueNode* front; // 队头
QueueNode* rear; // 队尾
} Queue;
// 初始化队列
void initQueue(Queue* q){
q->front = q->rear = NULL;
}
// 判断队列是否为空
bool isEmpty(Queue* q){
return q->front == NULL;
}
// 入队操作
void enqueue(Queue* q, State s){
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->state = s;
newNode->next = NULL;
if(q->rear == NULL){
q->front = q->rear = newNode;
return;
}
q->rear->next = newNode;
q->rear = newNode;
}
// 出队操作
State dequeue(Queue* q){
if(isEmpty(q)){
// 返回一个无效的状态
State invalid = {-1, -1, -1};
return invalid;
}
QueueNode* temp = q->front;
State s = temp->state;
q->front = q->front->next;
if(q->front == NULL){
q->rear = NULL;
}
free(temp);
return s;
}
int main(){
int m, n, t;
// 读取第一行,包含m, n, t
if(scanf("%d %d %d", &m, &n, &t) != 3){
// 输入格式错误,输出-1并退出
printf("-1\n");
return 0;
}
// 检查m和n是否有效
if(m <= 0 || n <=0){
printf("-1\n");
return 0;
}
// 初始化高度矩阵
int h[MAX_M][MAX_N];
// 读取m行,每行n个高度值
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(scanf("%d", &h[i][j]) !=1){
// 输入格式错误,输出-1并退出
printf("-1\n");
return 0;
}
// 检查高度是否在合理范围内
if(h[i][j] < 20 || h[i][j] > 4000){
printf("-1\n");
return 0;
}
}
}
int K; // 需要种植的树苗数量
// 读取需要种植的树苗数量K
if(scanf("%d", &K) !=1){
// 输入格式错误,输出-1并退出
printf("-1\n");
return 0;
}
// 如果需要种植的树苗数量大于坑位数量,无法种植,输出-1并退出
if(K > m * n){
printf("-1\n");
return 0;
}
// 初始化访问标记数组,维度为[m][n][4]
bool visited[MAX_M][MAX_N][MAX_USED +1];
for(int i =0; i < m; i++){
for(int j =0; j < n; j++){
for(int k =0; k <= MAX_USED; k++){
visited[i][j][k] = false;
}
}
}
// 初始化队列并加入起点状态
Queue q;
initQueue(&q);
State start = {0, 0, 0};
enqueue(&q, start);
visited[0][0][0] = true;
// 如果起点即为终点,移动次数为0
if(m ==1 && n ==1){
printf("0\n");
return 0;
}
// 定义移动方向:上、下、左、右
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int steps =0; // 移动次数初始化为0
// 开始进行广度优先搜索(BFS)
while(!isEmpty(&q)){
int size =0;
QueueNode* temp = q.front;
while(temp != NULL){
size++;
temp = temp->next;
}
steps++; // 每层代表一次移动
// 逐个处理当前层的所有状态
for(int i =0; i < size; i++){
State current = dequeue(&q);
int x = current.x;
int y = current.y;
int used = current.used;
// 遍历四个可能的移动方向
for(int dir =0; dir <4; dir++){
int new_x = x + dx[dir]; // 新的行坐标
int new_y = y + dy[dir]; // 新的列坐标
// 检查新位置是否在网格范围内
if(new_x <0 || new_x >= m || new_y <0 || new_y >=n){
continue; // 越界,跳过
}
// 计算高度差
int height_diff = abs(h[new_x][new_y] - h[x][y]);
// 初始化新的爆发机会次数
int new_used = used;
// 判断是否需要使用爆发机会
if(height_diff > t){
new_used +=1; // 使用一次爆发机会
if(new_used > MAX_USED){
continue; // 爆发机会已用完,无法移动
}
}
// 如果新状态未被访问过,则加入队列
if(!visited[new_x][new_y][new_used]){
visited[new_x][new_y][new_used] = true; // 标记为已访问
State new_state = {new_x, new_y, new_used};
enqueue(&q, new_state); // 将新状态加入队列
// 如果到达终点,返回当前的移动次数
if(new_x == m -1 && new_y == n -1){
printf("%d\n", steps);
return 0;
}
}
}
}
}
// 如果无法到达终点,输出-1
printf("-1\n");
return 0;
}
九、C++算法源码
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <climits>
using namespace std;
// 定义一个结构体来表示队伍的状态
struct State {
int x; // 当前行坐标
int y; // 当前列坐标
int used; // 已使用的爆发机会次数
};
// 定义一个辅助函数,判断是否可以以min_dist为最小间距种植K棵树
bool canPlaceTrees(const vector<vector<int>> &h, int m, int n, int t, int K, int start_x, int start_y, int min_dist, int current_used, int target_x, int target_y, vector<vector<vector<bool>>> &visited){
// 使用队列进行BFS,状态包括位置和已使用的爆发机会次数
queue<State> q;
State start_state = {start_x, start_y, current_used};
q.push(start_state);
visited[start_x][start_y][current_used] = true;
// 定义四个移动方向:上、下、左、右
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int steps = 0; // 移动次数
while(!q.empty()){
int size = q.size();
steps++; // 每层代表一次移动
for(int i =0; i < size; i++){
State current = q.front(); q.pop();
int x = current.x;
int y = current.y;
int used = current.used;
// 遍历四个方向
for(int dir =0; dir <4; dir++){
int new_x = x + dx[dir];
int new_y = y + dy[dir];
// 检查新位置是否在网格范围内
if(new_x <0 || new_x >= m || new_y <0 || new_y >=n){
continue; // 越界,跳过
}
// 计算高度差
int height_diff = abs(h[new_x][new_y] - h[x][y]);
// 初始化新的爆发机会次数
int new_used = used;
// 判断是否需要使用爆发机会
if(height_diff > t){
new_used +=1; // 使用一次爆发机会
if(new_used > 3){
continue; // 爆发机会已用完,无法移动
}
}
// 如果新状态未被访问过,则加入队列
if(!visited[new_x][new_y][new_used]){
visited[new_x][new_y][new_used] = true; // 标记为已访问
State new_state = {new_x, new_y, new_used};
q.push(new_state); // 将新状态加入队列
// 如果到达终点,返回当前的移动次数
if(new_x == target_x && new_y == target_y){
return true;
}
}
}
}
}
// 如果无法到达终点,返回false
return false;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int m, n, t;
// 读取第一行,包含m, n, t
cin >> m >> n >> t;
// 检查m和n是否有效
if(m <=0 || n <=0){
cout << "-1\n";
return 0;
}
// 初始化高度矩阵
vector<vector<int>> h(m, vector<int>(n, 0));
// 读取m行,每行n个高度值
for(int i =0; i <m; i++){
for(int j =0; j <n; j++){
cin >> h[i][j];
// 检查高度是否在合理范围内
if(h[i][j] <20 || h[i][j] >4000){
cout << "-1\n";
return 0;
}
}
}
int K; // 需要种植的树苗数量
cin >> K;
// 如果需要种植的树苗数量大于坑位数量,无法种植,输出-1并退出
if(K > m * n){
cout << "-1\n";
return 0;
}
// 定义一个三维访问标记数组,维度为[m][n][4]
// visited[x][y][k] 表示是否已经访问过位置(x,y)并且已使用k次爆发机会
vector<vector<vector<bool>>> visited(m, vector<vector<bool>>(n, vector<bool>(4, false)));
// 定义队列并初始化,将起点加入队列
queue<State> q;
State start = {0, 0, 0};
q.push(start);
visited[0][0][0] = true;
// 如果起点即为终点,移动次数为0
if(m ==1 && n ==1){
cout << "0\n";
return 0;
}
// 定义四个移动方向:上、下、左、右
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int steps =0; // 移动次数初始化为0
// 开始进行广度优先搜索(BFS)
while(!q.empty()){
int size = q.size(); // 当前层的状态数量
steps +=1; // 每层代表一次移动
// 逐个处理当前层的所有状态
for(int i =0; i < size; i++){
State current = q.front(); q.pop();
int x = current.x;
int y = current.y;
int used = current.used;
// 遍历四个可能的移动方向
for(int dir =0; dir <4; dir++){
int new_x = x + dx[dir]; // 新的行坐标
int new_y = y + dy[dir]; // 新的列坐标
// 检查新位置是否在网格范围内
if(new_x <0 || new_x >= m || new_y <0 || new_y >=n){
continue; // 越界,跳过
}
// 计算高度差
int height_diff = abs(h[new_x][new_y] - h[x][y]);
// 初始化新的爆发机会次数
int new_used = used;
// 判断是否需要使用爆发机会
if(height_diff > t){
new_used +=1; // 使用一次爆发机会
if(new_used > MAX_USED){
continue; // 爆发机会已用完,无法移动
}
}
// 如果新状态未被访问过,则加入队列
if(!visited[new_x][new_y][new_used]){
visited[new_x][new_y][new_used] = true; // 标记为已访问
State new_state = {new_x, new_y, new_used};
q.push(new_state); // 将新状态加入队列
// 如果到达终点,返回当前的移动次数
if(new_x == m -1 && new_y == n -1){
cout << steps << "\n";
return 0;
}
}
}
}
}
// 如果无法到达终点,输出-1
cout << "-1\n";
return 0;
}
🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 2024 E卷 200分)
🏆本文收录于,华为OD机试真题(Python/JS/C/C++)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。