
专栏导读
本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。
一、题目描述
小明自驾回家,为节省旅途成本,决定在网上挂出顺风车服务,有需要的乘客可自行申请服务,由小明决定谁能搭乘顺风车。
请你设计一个程序帮助小明将顺风车利益最大化,并返回最大的顺风车收益。
具体细节如下:
路线统一采用数值表示,小明的起点为 0,终点为 n。
搭乘顺风车的乘客起点和终点必须在 0 到 n 之间,且终点值大于起点值。
由于小明有家人同行,同一时间段只有一个乘客可以搭乘顺风车。
终点数值和起点数值差得到的是乘车距离,单位为公里;每公里顺风车小明收费 1 元。
二、输入描述
n:整数,表示小明的终点位置,值大于 1 且小于 1000。
passengers:乘客申请列表,每个乘客由 [起点, 终点] 组成,乘客数量不超过 300。
三、输出描述
整数,小明该趟顺风车的最大收益(最大总乘车距离)
四、测试用例
测试用例1:
1、输入
10
0,3 1,4 3,8 5,10
2、输出
8
3、说明
例如可以选择:
0 -> 3,收益 3
3 -> 8,收益 5
总收益:3 + 5 = 8
Java 实际测试结果:8
测试用例2:
1、输入
10
0,5 1,2 3,6 5,8 6,10
2、输出
9
3、说明
最优选择可以是:
0 -> 5,收益 5
6 -> 10,收益 4
总收益:5 + 4 = 9
五、解题思路
本题可以看作一个带权区间选择问题。每个乘客对应一个区间 [start, end],搭载该乘客的收益为 end - start。由于同一时间只能搭载一名乘客,因此选择的乘客区间不能重叠,目标是求所有合法组合中的最大总收益。
对于位置 i,有两种情况:
当前位置不结束任何新选择的乘客
直接继承前一个位置的最大收益:dp[i] = dp[i - 1]
选择一个终点为 i 的乘客 [start, i]
该乘客开始于 start,因此可以接在 dp[start] 对应的最优方案后面:
dp[i] = max(dp[i], dp[start] + i - start)
最终 dp[n] 就是最大顺风车收益。
为什么使用动态规划?
因为当前乘客能否被选择,只与其起点之前已经获得的最大收益有关,具有明显的最优子结构。使用 dp[start] 可以直接复用之前计算出的最优结果,避免枚举所有乘客组合。
六、Python算法源码
n = int(input().strip())
# passengers_by_end[end] 保存所有终点为 end 的乘客。
# 每个乘客使用 (start, end) 表示。
passengers_by_end = [[] for _ in range(n + 1)]
arr = input().strip().split()
for item in arr:
start, end = map(int, item.split(","))
# 题目本身保证数据合法。
# 这里仍然进行一次边界检查,防止异常输入导致数组越界。
if 0 <= start < end <= n:
passengers_by_end[end].append((start, end))
# dp[x] 表示:
# 小明到达位置 x 时,在 0~x 范围内能够获得的最大顺风车收益。
dp = [0] * (n + 1)
for x in range(1, n + 1):
# 情况1:
# 不选择任何在 x 位置结束的新乘客。
#
# 从 x-1 行驶到 x 如果没有搭载新乘客,
# 不会产生新的收益,所以直接继承 dp[x-1]。
dp[x] = dp[x - 1]
# 情况2:
# 枚举所有终点正好为 x 的乘客。
for start, end in passengers_by_end[x]:
# 当前乘客能够产生的收益就是乘车距离。
income = end - start
# 如果选择当前乘客 [start, end],
# 那么 start 之前已经结束的乘客可以继续保留。
#
# dp[start] 表示到达 start 时的最大收益,
# 所以当前方案的总收益为:
#
# dp[start] + 当前乘客收益
dp[x] = max(
dp[x],
dp[start] + income
)
print(dp[n])
七、JavaScript算法源码
const fs = require("fs");
const lines = fs.readFileSync(0, "utf8")
.trim()
.split(/\r?\n/);
const n = Number(lines[0].trim());
/*
* passengersByEnd[end] 保存所有终点为 end 的乘客。
* 每个乘客使用 [start, end] 表示。
*/
const passengersByEnd = Array.from(
{ length: n + 1 },
() => []
);
const arr = (lines[1] || "")
.trim()
.split(/\s+/);
for (const item of arr) {
if (!item) {
continue;
}
const [start, end] = item
.split(",")
.map(Number);
/*
* 题目保证数据合法,
* 这里额外判断一次,避免异常数据导致数组越界。
*/
if (start >= 0 && start < end && end <= n) {
passengersByEnd[end].push([start, end]);
}
}
/*
* dp[x] 表示:
* 小明到达位置 x 时,
* 在 0~x 范围内能够获得的最大顺风车收益。
*/
const dp = new Array(n + 1).fill(0);
for (let x = 1; x <= n; x++) {
/*
* 情况1:
* 不选择任何在 x 位置结束的新乘客。
*
* 从 x-1 到 x 没有产生新的乘客收益,
* 所以直接继承前一个位置的最优结果。
*/
dp[x] = dp[x - 1];
/*
* 情况2:
* 枚举所有终点正好为 x 的乘客。
*/
for (const [start, end] of passengersByEnd[x]) {
// 当前乘客产生的收益等于乘车距离。
const income = end - start;
/*
* 当前乘客从 start 开始,
* 因此可以与 start 之前的最优方案 dp[start]
* 无冲突地组合。
*/
dp[x] = Math.max(
dp[x],
dp[start] + income
);
}
}
/*
* 使用 stdout.write 输出纯文本。
* 可以避免某些特殊运行环境给 console.log(number)
* 添加终端颜色控制字符。
*/
process.stdout.write(String(dp[n]) + "\n");
八、C算法源码
#include <stdio.h>
#define MAX_N 1000
#define MAX_M 300
/*
* 一个乘客的信息。
*/
typedef struct {
int start;
int end;
/*
* next 表示:
* 下一个“终点相同”的乘客在 passengers 数组中的下标。
*
* -1 表示没有下一个。
*/
int next;
} Passenger;
int main() {
int n;
scanf("%d", &n);
/*
* head[end] 表示:
* 所有终点为 end 的乘客链表中的第一个乘客下标。
*
* 相当于 Java 中:
*
* passengersByEnd.get(end)
*/
int head[MAX_N + 1];
for (int i = 0; i <= n; i++) {
head[i] = -1;
}
Passenger passengers[MAX_M];
int passengerCount = 0;
int start;
int end;
/*
* 输入格式:
*
* 0,3 1,4 3,8 5,10
*
* scanf("%d,%d") 可以直接解析 start,end。
*
* %d 会自动忽略前面的空格和换行,
* 所以可以连续读取所有乘客。
*/
while (scanf("%d,%d", &start, &end) == 2) {
/*
* 题目本身保证输入合法。
* 这里仍然增加边界检查。
*/
if (start >= 0 &&
start < end &&
end <= n &&
passengerCount < MAX_M) {
passengers[passengerCount].start = start;
passengers[passengerCount].end = end;
/*
* 将当前乘客插入
* “终点为 end”的链表头部。
*/
passengers[passengerCount].next = head[end];
head[end] = passengerCount;
passengerCount++;
}
}
/*
* dp[x] 表示:
* 小明到达位置 x 时,
* 在 0~x 范围能够获得的最大顺风车收益。
*/
int dp[MAX_N + 1] = {0};
for (int x = 1; x <= n; x++) {
/*
* 情况1:
* 不选择任何在 x 位置结束的新乘客。
*
* 最大收益直接继承 dp[x-1]。
*/
dp[x] = dp[x - 1];
/*
* 情况2:
* 遍历所有终点为 x 的乘客。
*/
for (int index = head[x];
index != -1;
index = passengers[index].next) {
int start = passengers[index].start;
int end = passengers[index].end;
// 当前乘客的收益。
int income = end - start;
/*
* 如果选择当前乘客 [start, end],
* start 以前可以取得 dp[start] 的最大收益。
*
* 因此:
*
* 当前总收益 =
* dp[start] + (end - start)
*/
int candidate = dp[start] + income;
if (candidate > dp[x]) {
dp[x] = candidate;
}
}
}
printf("%d\n", dp[n]);
return 0;
}
九、C++算法源码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n;
/*
* passengersByEnd[end]
* 保存所有终点为 end 的乘客。
*
* pair.first = start
* pair.second = end
*/
vector<vector<pair<int, int>>> passengersByEnd(n + 1);
string item;
/*
* 每次读取一个字符串:
*
* 例如:
* 0,3
* 1,4
* 3,8
*/
while (cin >> item) {
// 查找逗号所在位置。
size_t pos = item.find(',');
if (pos == string::npos) {
continue;
}
/*
* 将逗号左右两部分分别转换为整数。
*/
int start = stoi(item.substr(0, pos));
int end = stoi(item.substr(pos + 1));
/*
* 题目本身保证数据合法。
* 这里仍增加边界判断。
*/
if (start >= 0 &&
start < end &&
end <= n) {
passengersByEnd[end].push_back(
{start, end}
);
}
}
/*
* dp[x]:
* 小明到达位置 x 时,
* 在 0~x 范围内能够获得的最大顺风车收益。
*/
vector<int> dp(n + 1, 0);
for (int x = 1; x <= n; x++) {
/*
* 情况1:
* 不选择任何在 x 结束的新乘客。
*
* 当前最大收益直接继承 dp[x-1]。
*/
dp[x] = dp[x - 1];
/*
* 情况2:
* 枚举所有终点为 x 的乘客。
*/
for (const auto& passenger : passengersByEnd[x]) {
int start = passenger.first;
int end = passenger.second;
// 当前乘客的收益就是乘车距离。
int income = end - start;
/*
* 当前乘客从 start 上车,
* 所以可以接在 dp[start] 所表示的
* 最优方案之后。
*
* 两部分不会产生乘车区间冲突。
*/
dp[x] = max(
dp[x],
dp[start] + income
);
}
}
cout << dp[n] << '\n';
return 0;
}
🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 新系统 200分)
🏆本文收录于,华为OD机试真题(Python/JS/C/C++)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

45万+

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



