山区救援路线规划(2026.11.01)
2026年最新华为OD机考题库(按考点分类): 2026年最新华为OD机考题库
题目描述
某山区建立了一套应急救援通道网络。由于地形限制,从指挥中心(根节点)出发,每条通道在岔路口只能分为两条分支,最终抵达不同救援集结点(叶子节点)。
每个通道节点都有对应的海拔高度(米)。救援队从指挥中心出发前往集结点,需要权衡两方面安全因素:
- 体力消耗:上坡比下坡更耗体力。将每段路程的体力消耗定义为:
- 上坡(海拔升高):α×∣高差∣
- 下坡(海拔降低):β×∣高差∣
- 一条路线的总体力消耗为各段消耗之和
- 海拔跨度:路线上最高海拔与最低海拔的差值不能超过安全阈值 S,否则气压变化过大,队员有高原反应风险。
一条「路线」指从根节点出发、走到某个叶子节点的路径(叶子节点指左右子节点都不存在的节点)。你的任务是:在所有海拔跨度不超过 S 的路线中,找出总体力消耗最小的路线。
若存在多条体力消耗相同的路线,选择节点编号序列字典序较小的那条:按顺序逐个比较两条序列的元素,第一个不同的位置上元素更小者更小;若一条序列是另一条序列的前缀,则较短的序列更小。
输入描述
共 5 个参数,依次为:n(节点数)、alpha(上坡系数)、beta(下坡系数)、S(海拔跨度阈值)、nodes(n 行 4 列的二维数组,每行为 [id, value, left, right],id 从 1 到 n 按升序给出,left/right 为 -1 表示无对应子节点)。
输出描述
返回路线经过的节点 id 序列(从根 1 开始到叶子结束)组成的数组;若无合法路线返回空数组 []。
样例
用例输入 1
7,3,1,800,[[1,2000,2,3],[2,1500,4,5],[3,2400,6,7],[4,900,-1,-1],[5,2300,-1,-1],[6,2100,-1,-1],[7,2800,-1,-1]]
用例输出 1
[1,3,6]
用例解释 1
[1:2000]
/ \
[2:1500] [3:2400]
/ \ / \
[4:900] [5:2300] [6:2100] [7:2800]
4 条路线的分析(α=3,β=1,S=800):
1->2->4:下 500(×1=500)+ 下 600(×1=600)= 1100;2000−900=1100,跨度超限1->2->5:下 500(×1=500)+ 上 800(×3=2400)= 2900;2300−1500=800,合法1->3->6:上 400(×3=1200)+ 下 300(×1=300)= 1500;2400−2100=300,合法1->3->7:上 400(×3=1200)+ 上 400(×3=1200)= 2400;2800−2000=800,合法
1->2->4 虽然体力消耗最低(1100),但海拔跨度 1100 超过安全阈值 800,被淘汰;合法路线中 1->3->6 总消耗 1500 最小,故选之。
用例输入 2
3,1,1,100,[[1,2000,2,3],[2,1000,-1,-1],[3,3000,-1,-1]]
用例输出 2
[]
用例解释 2
[1: 2000]
/ \
[2: 1000] [3: 3000]
2 条路线分析(α=1,β=1,S=100):
1->2:海拔跨度 1000 > 安全阈值 100,不合法1->3:海拔跨度 1000 > 安全阈值 100,不合法
故没有合法路线,按约定输出空数组 []。
来源
本题来源于网络、非官方材料、仅供学习交流,侵删
🧠 解题思路
第一步:题目在问什么
从根(1 号)走到某个叶子(左右孩子都没有的节点)就是一条路线。每走一段:上坡消耗 alpha × 高差,下坡消耗 beta × 高差。路线上最高海拔减最低海拔不能超过 S。在合法路线里找总消耗最小的;消耗相同时,选节点编号序列字典序更小的;没有合法路线返回空数组。
第二步:每个节点只需要记三个数
树上每个节点只有一个父节点,从根走到它的路线是唯一的。所以“走到这个节点为止”的情况可以直接记在节点上:
- cost[c]:从根走到 c 的总消耗;
- hi[c]、lo[c]:这一路上的最高、最低海拔。
它们都能从父节点 u 一步算出来:
- cost[c] = cost[u] + 这一段的消耗(val[c] > val[u] 是上坡,用 alpha 乘高差;否则是下坡,用 beta 乘高差);
- hi[c] = max(hi[u], val[c]),lo[c] = min(lo[u], val[c])。
再用 parent[c] = u 记下父节点,最后可以从叶子倒推回根得到整条路线,不用在每个节点都复制一份路径。复制路径的话,树是一条长链时内存会爆。
第三步:DFS 走遍所有节点
用**深度优先搜索(DFS)**从根往下走,每个节点算出上面三个数。走到叶子时,如果 hi − lo ≤ S,这条路线合法,就和当前最优比较。
树最深可达 10⁴ 层,Python 递归会超过默认深度上限,所以用栈代替递归:弹出一个节点,算好它孩子的三个数,再把孩子压进栈。
第四步:消耗相同时怎么选字典序小的
展开一个节点时,先访问编号小的孩子(栈是后进先出,所以先压编号大的、后压编号小的)。这样 DFS 遇到叶子的先后顺序,正好就是路线字典序从小到大的顺序。
所以只在“消耗严格更小”时才替换答案:消耗相同时,先遇到的那条字典序更小,保留它就对了。
最后从最优叶子沿 parent 一路倒推回根,再把顺序反过来,就是答案路线。
用样例 1 走一遍
alpha = 3,beta = 1,S = 800。节点海拔:1:2000,2:1500,3:2400,4:900,5:2300,6:2100,7:2800;1 的孩子是 2、3,2 的孩子是 4、5,3 的孩子是 6、7。
| 路线 | 每段消耗 | 总消耗 | 最高 − 最低 | 合法? |
|---|---|---|---|---|
| 1 → 2 → 4 | 下 500 → 500,下 600 → 600 | 1100 | 2000 − 900 = 1100 | 超过 800,不合法 |
| 1 → 2 → 5 | 下 500 → 500,上 800 → 2400 | 2900 | 2300 − 1500 = 800 | 合法,当前最优 |
| 1 → 3 → 6 | 上 400 → 1200,下 300 → 300 | 1500 | 2400 − 2000 = 400 | 合法,更小,替换 |
| 1 → 3 → 7 | 上 400 → 1200,上 400 → 1200 | 2400 | 2800 − 2000 = 800 | 合法,但不更小 |
表里按 DFS 遇到叶子的顺序列出。最优叶子是 6,倒推 6 → 3 → 1,反过来得到 [1, 3, 6]。
⚠️ 易错点
- 叶子要左右孩子都是 −1,只有一个孩子的节点不是叶子。
- 海拔跨度是整条路线的最高减最低,不是相邻两点的高差。
- 上坡用 alpha、下坡用 beta,别弄反。
- 消耗相同时比较整条路线的字典序:先访问编号小的孩子,并且只在严格更小时替换。
- 不要在每个节点复制整条路径,长链形状的树会让时间和内存变成 O(n·h)。
- 没有合法路线时返回空数组。
⏱️ 复杂度
时间复杂度:O(n),每个节点入栈、出栈各一次,最后倒推路线 O(h),h 是树高。
空间复杂度:O(n),各节点的 cost、hi、lo、parent 和栈。
💻 参考代码(Java)
import java.util.*;
class Solution {
public int[] planRescueRoute(int n, int alpha, int beta, int S, int[][] nodes) {
// 按 id 建立每个节点的海拔、左子、右子
int[] val = new int[n + 1];
int[] lc = new int[n + 1];
int[] rc = new int[n + 1];
for (int[] row : nodes) {
val[row[0]] = row[1];
lc[row[0]] = row[2];
rc[row[0]] = row[3];
}
// 每个节点到根的路线唯一,累计消耗、最高、最低海拔直接按节点存,并记录父节点
int[] parent = new int[n + 1];
int[] hi = new int[n + 1];
int[] lo = new int[n + 1];
long[] cost = new long[n + 1];
hi[1] = lo[1] = val[1];
int bestLeaf = -1;
long bestCost = 0;
// 迭代 DFS(树高可达 10^4,不用递归):先访问编号小的孩子,叶子按路线字典序从小到大出现
int[] st = new int[n + 1];
int top = 0;
st[top++] = 1;
while (top > 0) {
int u = st[--top];
int l = lc[u], r = rc[u];
if (l == -1 && r == -1) {
// 消耗严格更小才替换:消耗相同时先出现的路线字典序更小
if (hi[u] - lo[u] <= S && (bestLeaf == -1 || cost[u] < bestCost)) {
bestLeaf = u;
bestCost = cost[u];
}
continue;
}
int first = l, second = r; // first 为编号大的孩子,先压栈
if (first != -1 && second != -1 && first < second) {
first = r;
second = l;
}
for (int c : new int[]{first, second}) {
if (c == -1) continue;
int diff = val[c] - val[u];
parent[c] = u;
cost[c] = cost[u] + (diff > 0 ? (long) alpha * diff : (long) beta * (-diff));
hi[c] = Math.max(hi[u], val[c]);
lo[c] = Math.min(lo[u], val[c]);
st[top++] = c;
}
}
if (bestLeaf == -1) return new int[0];
// 沿父节点从叶子倒推回根,倒序填入数组
int len = 0;
for (int u = bestLeaf; u != 0; u = parent[u]) len++;
int[] path = new int[len];
for (int u = bestLeaf, i = len - 1; u != 0; u = parent[u], i--) path[i] = u;
return path;
}
}
💻 参考代码(C++)
class Solution {
public:
vector<int> planRescueRoute(int n, int alpha, int beta, int S, vector<vector<int>> nodes) {
// 按 id 建立每个节点的海拔、左子、右子
vector<int> val(n + 1, 0), lc(n + 1, -1), rc(n + 1, -1);
for (auto &row : nodes) {
val[row[0]] = row[1];
lc[row[0]] = row[2];
rc[row[0]] = row[3];
}
// 每个节点到根的路线唯一,累计消耗、最高、最低海拔直接按节点存,并记录父节点
vector<int> parent(n + 1, 0), hi(n + 1, 0), lo(n + 1, 0);
vector<long long> cost(n + 1, 0);
hi[1] = lo[1] = val[1];
int bestLeaf = -1;
long long bestCost = 0;
// 迭代 DFS(树高可达 10^4,不用递归):先访问编号小的孩子,叶子按路线字典序从小到大出现
vector<int> st = {1};
while (!st.empty()) {
int u = st.back();
st.pop_back();
int l = lc[u], r = rc[u];
if (l == -1 && r == -1) {
// 消耗严格更小才替换:消耗相同时先出现的路线字典序更小
if (hi[u] - lo[u] <= S && (bestLeaf == -1 || cost[u] < bestCost)) {
bestLeaf = u;
bestCost = cost[u];
}
continue;
}
int first = l, second = r; // first 为编号大的孩子,先压栈
if (first != -1 && second != -1 && first < second) swap(first, second);
for (int c : {first, second}) {
if (c == -1) continue;
int diff = val[c] - val[u];
parent[c] = u;
cost[c] = cost[u] + (diff > 0 ? (long long) alpha * diff : (long long) beta * (-diff));
hi[c] = max(hi[u], val[c]);
lo[c] = min(lo[u], val[c]);
st.push_back(c);
}
}
vector<int> path;
if (bestLeaf == -1) return path;
// 沿父节点从叶子倒推回根,再反转得到路线
for (int u = bestLeaf; u != 0; u = parent[u]) path.push_back(u);
reverse(path.begin(), path.end());
return path;
}
};
💻 参考代码(Python)
class Solution:
def planRescueRoute(self, n, alpha, beta, S, nodes):
# 按 id 建立每个节点的海拔、左子、右子
val = [0] * (n + 1)
left = [-1] * (n + 1)
right = [-1] * (n + 1)
for row in nodes:
idx = row[0]
val[idx] = row[1]
left[idx] = row[2]
right[idx] = row[3]
# 每个节点到根的路线唯一,累计消耗、最高、最低海拔直接按节点存,并记录父节点
parent = [0] * (n + 1)
cost = [0] * (n + 1)
hi = [0] * (n + 1)
lo = [0] * (n + 1)
hi[1] = lo[1] = val[1]
best_leaf = -1
best_cost = 0
# 迭代 DFS(树高可达 10^4,不用递归):先访问编号小的孩子,叶子按路线字典序从小到大出现
stack = [1]
while stack:
u = stack.pop()
l, r = left[u], right[u]
if l == -1 and r == -1:
# 消耗严格更小才替换:消耗相同时先出现的路线字典序更小
if hi[u] - lo[u] <= S and (best_leaf == -1 or cost[u] < best_cost):
best_leaf = u
best_cost = cost[u]
continue
children = [c for c in (l, r) if c != -1]
children.sort(reverse=True) # 编号大的先压栈,编号小的先弹出
for c in children:
diff = val[c] - val[u]
parent[c] = u
cost[c] = cost[u] + (alpha * diff if diff > 0 else beta * (-diff))
hi[c] = max(hi[u], val[c])
lo[c] = min(lo[u], val[c])
stack.append(c)
if best_leaf == -1:
return []
# 沿父节点从叶子倒推回根,再反转得到路线
path = []
u = best_leaf
while u != 0:
path.append(u)
u = parent[u]
path.reverse()
return path
💻 参考代码(JavaScript)
class Solution {
planRescueRoute(n, alpha, beta, S, nodes) {
// 按 id 建立每个节点的海拔、左子、右子
const val = new Array(n + 1).fill(0);
const lc = new Array(n + 1).fill(-1);
const rc = new Array(n + 1).fill(-1);
for (const row of nodes) {
val[row[0]] = row[1];
lc[row[0]] = row[2];
rc[row[0]] = row[3];
}
// 每个节点到根的路线唯一,累计消耗、最高、最低海拔直接按节点存,并记录父节点
const parent = new Array(n + 1).fill(0);
const cost = new Array(n + 1).fill(0);
const hi = new Array(n + 1).fill(0);
const lo = new Array(n + 1).fill(0);
hi[1] = lo[1] = val[1];
let bestLeaf = -1;
let bestCost = 0;
// 迭代 DFS(树高可达 10^4,不用递归):先访问编号小的孩子,叶子按路线字典序从小到大出现
const stack = [1];
while (stack.length > 0) {
const u = stack.pop();
const l = lc[u], r = rc[u];
if (l === -1 && r === -1) {
// 消耗严格更小才替换:消耗相同时先出现的路线字典序更小
if (hi[u] - lo[u] <= S && (bestLeaf === -1 || cost[u] < bestCost)) {
bestLeaf = u;
bestCost = cost[u];
}
continue;
}
const children = [l, r].filter((c) => c !== -1).sort((a, b) => b - a); // 编号大的先压栈
for (const c of children) {
const diff = val[c] - val[u];
parent[c] = u;
cost[c] = cost[u] + (diff > 0 ? alpha * diff : beta * (-diff));
hi[c] = Math.max(hi[u], val[c]);
lo[c] = Math.min(lo[u], val[c]);
stack.push(c);
}
}
if (bestLeaf === -1) return [];
// 沿父节点从叶子倒推回根,再反转得到路线
const path = [];
for (let u = bestLeaf; u !== 0; u = parent[u]) path.push(u);
return path.reverse();
}
}
module.exports = { Solution };
4062



被折叠的 条评论
为什么被折叠?



