华为OD机试 - LLM推理批次最大化 - 贪心算法(Python/JS/C/C++/Go 新系统 100分)

本题为经典的区间调度问题,目标是在N个闭区间请求中选出最多互不重叠的区间,以最大化大语言模型推理批次吞吐量。采用贪心算法,按右端点升序排序后遍历选择,确保每次选取结束最早的可行区间。时间复杂度O(N log N),空间复杂度O(N),适用于N≤10^5规模。

在这里插入图片描述

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

专栏导读

本专栏收录于《华为OD机试真题(Java/Python/JS/C/C++/Go)》。

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

一、题目内容

大语言模型推理时,显存中有一个 KV Cache,用于存储各请求的键值对。现有 N N N 个推理请求排队,第 i i i 个请求需要占用 KV Cache 中一段连续位置 [ L i , R i ] [L_i, R_i] [Li​,Ri​]。每个位置同一时间只能分配给一个请求。请选择尽可能多的请求,使它们的区间互不重叠,从而最大化批次的吞吐量。

补充说明:

  1. [1, 3] 和 [3, 5] 被认为是重叠。

二、输入描述

  • requests:由 N N N 个推理请求构成的二维数组,requests[i] 表示第 i i i 个推理请求,其内容为 [ L i , R i ] [L_i, R_i] [Li​,Ri​]。
  • 数组长度 N N N 满足 1 ≤ N ≤ 10 5 1 \le N \le 10^5 1≤N≤105。
  • 每个位置 [ L i , R i ] [L_i, R_i] [Li​,Ri​] 满足 0 ≤ L i ≤ R i ≤ 10 9 0 \le L_i \le R_i \le 10^9 0≤Li​≤Ri​≤109。

三、输出描述

一个整数,表示最多可选多少个不重叠区间的请求。

四、测试用例

测试用例1:

1、输入

1,3 2,5 4,7 6,9 8,10 11,12

2、输出

4

3、说明

可以选择 [1,3]、[4,7]、[8,10]、[11,12],共 4 个不重叠请求。其他选择无法获得更多请求。

测试用例2:

1、输入

1,3 2,4 3,3 4,4

2、输出

2

3、说明

可以选择 [3,3] 和 [4,4]。虽然 [1,3] 与 [3,3] 只在位置 3 相交,但仍属于重叠,因此不能同时选择。

五、解题思路

解题思路

1. 问题分析

本题是经典的区间调度问题。给定 N 个闭区间,需要选择尽可能多的区间,使任意两个选中区间都不重叠。由于区间包含左右端点,因此两个区间只有在前一个区间的右端点严格小于后一个区间的左端点时,才能同时被选中。

2. 数据结构选择

使用 ArrayList<long[]> 存储请求区间,每个long 数组包含左端点和右端点。该结构存储简单,支持遍历与排序。考虑到 N 最大为 10^5,使用数组列表能够满足存储要求。

3. 算法设计

采用贪心算法,首先按照区间右端点升序排序。当右端点相同时,按照左端点升序排序。

排序完成后,从左到右依次检查每个区间。使用 lastRight 保存最后一个已选区间的右端点,初始值设置为 -1。

如果当前区间的左端点大于 lastRight,说明两者不存在重叠,可以选择当前区间,将答案加一,并更新 lastRight。

否则跳过当前区间,继续检查后面的区间。

4. 贪心策略的正确性

选择结束最早的区间,可以为后续区间保留尽可能大的可用位置范围。假设某个最优方案首先选择了一个结束更晚的区间,可以将其替换成当前结束最早的可选区间,而不会影响后续区间的选择。

因此,每次选择结束最早的可用区间不会降低最优解,最终能够得到最多的不重叠请求数量。

5. 复杂度分析

时间复杂度:O(N log N),主要来自区间排序。

排序后的贪心遍历为 O(N)。

空间复杂度:O(N),用于保存所有请求区间。

额外的贪心状态仅需要 O(1) 空间。

该算法可以高效处理 N <= 10^5 的数据规模。

六、Python算法源码

import re
import sys


def main():

    # 1. 读取控制台输入
    # 使用正则表达式提取所有非负整数
    # 支持 1,3 2,5 以及 [[1,3],[2,5]] 等格式
    numbers = [
        int(s) for s in re.findall(r"\d+", sys.stdin.read())
    ]

    # 2. 每两个整数构成一个请求区间
    requests = [
        (numbers[i], numbers[i + 1])
        for i in range(0, len(numbers) - 1, 2)
    ]

    # 3. 按右端点升序排序
    # 如果右端点相同,则按照左端点升序排序
    # 这样优先考虑结束位置最早的请求
    requests.sort(key=lambda item: (item[1], item[0]))

    # 4. 记录上一个已选区间的右端点
    # 因为左端点最小为 0,所以初始化为 -1
    last_right = -1
    answer = 0

    # 5. 贪心选择不重叠的区间
    for left, right in requests:

        # 闭区间端点相等也算重叠
        # 因此必须使用严格大于号
        if left > last_right:

            # 当前请求能够加入批次
            answer += 1

            # 更新最后一个已选区间的结束位置
            last_right = right

    # 6. 输出最多可以选择的请求数量
    print(answer)


if __name__ == "__main__":
    main()

七、JavaScript算法源码

const fs = require('fs');

// 1. 读取控制台全部输入
// 通过正则表达式提取所有非负整数
// 兼容空格、换行、逗号和二维数组格式
const numbers = (
    fs.readFileSync(0, 'utf8').match(/\d+/g) || []
).map(Number);

// 2. 将输入数据转换成二维区间数组
const requests = [];

for (let i = 0; i + 1 < numbers.length; i += 2) {
    requests.push([
        numbers[i],
        numbers[i + 1]
    ]);
}

// 3. 按照区间右端点升序排序
// 如果右端点相同,则比较左端点
// 每次优先考虑结束最早的请求
requests.sort((a, b) => {
    return a[1] - b[1] || a[0] - b[0];
});

// 4. 初始化贪心算法的状态
let lastRight = -1;
let answer = 0;

// 5. 遍历排序后的所有请求
for (const [left, right] of requests) {

    // 只有当前左端点严格大于上一个右端点
    // 才表示两个闭区间没有重叠
    if (left > lastRight) {

        // 选择当前请求
        answer++;

        // 更新最后一个已选区间的右端点
        lastRight = right;
    }
}

// 6. 输出最大请求数量
console.log(answer);

八、C算法源码

#include <stdio.h>
#include <stdlib.h>

// 1. 定义区间结构体
// 分别保存请求的左端点和右端点
typedef struct {
    long long left;
    long long right;
} Interval;

// 2. 读取下一个非负整数
// 跳过逗号、空格、换行以及方括号等分隔符
// 返回 1 表示读取成功,返回 0 表示输入结束
static int nextNumber(long long *value) {

    int ch = getchar();

    // 跳过所有非数字字符
    while (ch != EOF && (ch < '0' || ch > '9')) {
        ch = getchar();
    }

    if (ch == EOF) {
        return 0;
    }

    long long result = 0;

    // 将连续的数字字符转换为整数
    while (ch >= '0' && ch <= '9') {
        result = result * 10 + (ch - '0');
        ch = getchar();
    }

    *value = result;
    return 1;
}

// 3. 定义 qsort 使用的比较函数
// 优先按照右端点升序排序
// 右端点相同时按照左端点升序排序
static int byRight(const void *p, const void *q) {

    const Interval *a = (const Interval *)p;
    const Interval *b = (const Interval *)q;

    if (a->right != b->right) {
        return (a->right > b->right)
             - (a->right < b->right);
    }

    return (a->left > b->left)
         - (a->left < b->left);
}

int main(void) {

    // 4. 申请用于保存请求的内存
    // 根据题目约束,最多有 100000 个请求
    const size_t capacity = 100000;

    Interval *requests = malloc(
        capacity * sizeof(*requests)
    );

    if (requests == NULL) {
        return 1;
    }

    size_t count = 0;
    long long left, right;

    // 5. 持续读取请求直到输入结束
    // 每两个整数构成一个区间
    while (nextNumber(&left) && nextNumber(&right)) {

        if (count >= capacity) {
            free(requests);
            return 1;
        }

        requests[count].left = left;
        requests[count].right = right;

        count++;
    }

    // 6. 按区间右端点升序排序
    // 优先选择结束时间早的请求
    qsort(
        requests,
        count,
        sizeof(*requests),
        byRight
    );

    // 7. 初始化贪心选择状态
    long long lastRight = -1;
    int answer = 0;

    // 8. 遍历排序后的所有区间
    for (size_t i = 0; i < count; i++) {

        // 当前左端点必须严格大于
        // 最后一个已选区间的右端点
        if (requests[i].left > lastRight) {

            // 当前请求可以加入批次
            answer++;

            // 更新最后一个已选区间的结束位置
            lastRight = requests[i].right;
        }
    }

    // 9. 输出结果并释放内存
    printf("%d\n", answer);

    free(requests);

    return 0;
}

九、C++算法源码

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <vector>

using namespace std;

// 1. 定义请求区间结构体
struct Interval {
    long long left;
    long long right;
};

// 2. 从标准输入读取下一个非负整数
// 自动跳过逗号、空格、换行、方括号等字符
// 如果成功读取数字返回 true,否则返回 false
bool nextNumber(long long &value) {

    int ch = cin.get();

    // 跳过非数字字符
    while (ch != EOF && (ch < '0' || ch > '9')) {
        ch = cin.get();
    }

    if (ch == EOF) {
        return false;
    }

    value = 0;

    // 解析连续的数字字符
    while (ch >= '0' && ch <= '9') {
        value = value * 10 + (ch - '0');
        ch = cin.get();
    }

    return true;
}

int main() {

    // 3. 优化标准输入输出性能
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    vector<Interval> requests;

    long long left, right;

    // 4. 读取所有请求
    // 每两个数字表示一个闭区间 [left, right]
    while (nextNumber(left) && nextNumber(right)) {
        requests.push_back({left, right});
    }

    // 5. 按右端点升序排序
    // 右端点相同时,按照左端点升序排序
    // 尽量优先选择结束更早的请求
    sort(
        requests.begin(),
        requests.end(),
        [](const Interval &a, const Interval &b) {

            if (a.right != b.right) {
                return a.right < b.right;
            }

            return a.left < b.left;
        }
    );

    // 6. 初始化贪心算法状态
    long long lastRight = -1;
    int answer = 0;

    // 7. 依次检查排序后的请求
    for (const auto &item : requests) {

        // 两个闭区间的端点相同也属于重叠
        // 因此当前左端点必须严格大于 lastRight
        if (item.left > lastRight) {

            // 选择当前请求
            answer++;

            // 更新最后一个已选区间的结束位置
            lastRight = item.right;
        }
    }

    // 8. 输出最大可选择请求数量
    cout << answer << '\n';

    return 0;
}

十、Go算法源码

package main

import (
    "bufio"
    "fmt"
    "os"
    "sort"
)

// 1. 定义请求区间结构体
// left 和 right 分别代表左右端点
type Interval struct {
    left  int64
    right int64
}

// 2. 从输入流中读取下一个非负整数
// 跳过空格、逗号、换行、方括号等分隔符
// 返回值中的 bool 表示是否成功读取
func nextNumber(reader *bufio.Reader) (int64, bool) {

    for {

        ch, err := reader.ReadByte()

        // 输入结束,没有更多数字
        if err != nil {
            return 0, false
        }

        // 跳过非数字字符
        if ch < '0' || ch > '9' {
            continue
        }

        // 找到数字后,开始计算整数值
        value := int64(ch - '0')

        for {
            ch, err = reader.ReadByte()

            // 数字结束,返回本次读取结果
            if err != nil || ch < '0' || ch > '9' {
                return value, true
            }

            value = value*10 + int64(ch-'0')
        }
    }
}

func main() {

    // 3. 创建标准输入缓冲区
    reader := bufio.NewReader(os.Stdin)

    // 创建切片,用于存储所有请求区间
    // 预分配容量,减少动态扩容次数
    requests := make([]Interval, 0, 100000)

    // 4. 读取所有请求
    // 每两个整数组成一个区间
    for {

        left, ok := nextNumber(reader)

        if !ok {
            break
        }

        right, ok := nextNumber(reader)

        if !ok {
            break
        }

        requests = append(
            requests,
            Interval{left, right},
        )
    }

    // 5. 按区间右端点升序排序
    // 如果右端点相同,则按照左端点升序排序
    // 优先考虑结束最早的请求
    sort.Slice(requests, func(i, j int) bool {

        if requests[i].right != requests[j].right {
            return requests[i].right < requests[j].right
        }

        return requests[i].left < requests[j].left
    })

    // 6. 初始化贪心算法状态
    lastRight := int64(-1)
    answer := 0

    // 7. 依次选择不重叠的请求
    for _, request := range requests {

        // 注意:区间是闭区间
        // 当前左端点必须严格大于上一个右端点
        if request.left > lastRight {

            // 当前请求可以被选择
            answer++

            // 更新最后一个已选区间的结束位置
            lastRight = request.right
        }
    }

    // 8. 输出最大请求数量
    fmt.Println(answer)
}

🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++/Go 新系统 200分)

🏆本文收录于,华为OD机试真题(Python/JS/C/C++/Go)

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

哪 吒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值