华为OD机试 - 小明的顺风车 - 动态规划(Python/JS/C/C++ 新系统 200分)

在这里插入图片描述

华为OD机试 新系统 统一考试题库清单(持续收录中)以及考点说明(Python/JS/C/C++)

专栏导读

本专栏收录于《华为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算法的适用场景,发现新题目,随时更新。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哪 吒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值