华为OD机试 - 均衡调度 - 数学推导(Python/JS/C/C++/Go 新系统 100分)

本博客详解华为OD机试真题'均衡调度',核心是通过一次任务交换使两队列总耗时相等。基于数学推导得出交换条件:target = (sumA - sumB) / 2 + B[j],进而利用定长数组(长度100)实现O(1)耗时映射查找,避免哈希冲突。算法时间复杂度O(n+m),空间复杂度O(1),适用于任务耗时范围1~99的约束场景。

在这里插入图片描述

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

专栏导读

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

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

一、题目内容

已知存在两个任务队列 A A A、 B B B,队列成员表示单个任务耗时;为了缩短整体运行时间,需要队列 A A A 和队列 B B B 中任务的总耗时一致。

初始时队列 A A A 和队列 B B B 的任务总耗时不同,要求只进行一次任务交换:从 A A A 中选择一个任务,从 B B B 中选择一个任务,两者互换;交换后要求两个队列的任务总耗时相等。

请你找出队列 A A A 和队列 B B B 中需要交换的任务位置 [ i , j ] [i,j] [i,j](数组下标,从 0 开始),规则说明:

  1. 单个任务耗时取值 0 < T < 100 0 < T < 100 0<T<100。

  2. 用例保证均有解,且满足条件的下标对 [ i , j ] [i,j] [i,j] 唯一。

  3. 队列 A A A 和队列 B B B 长度为 1 ∼ 50 1 \sim 50 1∼50。

二、输入描述

整型数组 A A A,由队列 A A A 中的任务耗时构成的数组。

整型数组 B B B,由队列 B B B 中的任务耗时构成的数组。

三、输出描述

数组 [ i , j ] [i,j] [i,j],其中 i i i 是 A A A 中参与交换任务的原始下标, j j j 是 B B B 中参与交换任务的原始下标,下标从 0 开始。

四、测试用例

测试用例1:

1、输入

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

2、输出

1,0

3、说明

基础场景:A 总耗时大于 B

初始总耗时为 12 和 10。交换 A[1]=2 与 B[0]=1 后,两个队列的总耗时均变为 11。

测试用例2:

1、输入

1,15,9
2,4,6,8,25

2、输出

1,4

3、说明

反向场景:A 总耗时小于 B

初始总耗时为 25 和 45。交换 A[1]=15 与 B[4]=25 后,两个队列的总耗时均变为 35。验证负数差值的处理。

五、解题思路

解题思路:数学推导 + 定长数组映射

1、数学公式推导

首先遍历两个任务队列,计算总耗时 sumA 和 sumB。假设交换的任务分别为 A[i] 和 B[j],交换后两个队列总耗时相等,即:

sumA - A[i] + B[j] = sumB - B[j] + A[i]

整理得到:

A[i] - B[j] = (sumA - sumB) / 2

因此,当确定 A 中的任务时,可以直接计算 B 中需要匹配的任务耗时,而不必枚举所有交换组合。

2、数据结构选择

由于题目规定单个任务耗时在 1~99 之间,采用长度为 100 的整型数组 indexInB 建立映射。数组下标表示任务耗时,数组元素表示该任务在 B 中的原始下标。

初始化所有元素为 -1,表示对应耗时不存在。遍历 B,将每个任务的原始下标记录下来。

该结构比普通哈希表更简单,没有哈希冲突问题,且能够直接按耗时进行常数时间查找。

3、算法实现

遍历队列 A,对每个位置 i,计算目标耗时:

target = A[i] - (sumA - sumB) / 2

检查 target 是否在 1~99 范围内,以及 indexInB[target] 是否不为 -1。如果满足条件,则返回 [i, indexInB[target]]。

若两个队列总耗时之差为奇数,则无法通过交换整数任务达到平衡。题目保证存在唯一解,因此正常情况下总能找到结果。

4、正确性与复杂度

算法完全依据交换后总耗时相等的充要条件查找,因此找到的下标对一定满足要求。

相比 O(n×m) 的双重循环,数组映射避免了重复比较;相比排序算法,不会改变任务的原始下标。

  • 时间复杂度:O(n+m+100),即 O(n+m)。
  • 空间复杂度:O(100),即 O(1)。
  • n、m 分别为两个任务队列的长度。

该算法实现简单、性能稳定,适合本题给定的数据范围。

六、Python算法源码


import sys


def parse_array(line):
    # 输入格式为逗号分隔的整数,同时允许任务耗时两侧存在空格。
    return [int(part.strip()) for part in line.strip().split(',')]


def find_swap(a, b):
    sum_a, sum_b = sum(a), sum(b)

    # 交换 a[i]、b[j] 后总耗时相等,可以推导出:
    # a[i] - b[j] = (sum_a - sum_b) / 2。
    diff = sum_a - sum_b
    if diff % 2 != 0:
        return -1, -1

    half = diff // 2

    # 耗时范围为 1~99,直接用下标存 B 中对应任务的原始位置。
    # -1 代表没有这个任务耗时,因此无需对队列进行排序。
    index_in_b = [-1] * 100

    for j, value in enumerate(b):
        index_in_b[value] = j

    # 对 A 中每项算出 B 里需要的耗时,再 O(1) 查询其位置。
    for i, value in enumerate(a):
        target = value - half

        if 1 <= target <= 99 and index_in_b[target] != -1:
            return i, index_in_b[target]

    return -1, -1  # 题目保证有唯一解。


def main():
    a = parse_array(sys.stdin.readline())
    b = parse_array(sys.stdin.readline())

    i, j = find_swap(a, b)
    print(f"{i},{j}")


if __name__ == "__main__":
    main()

七、JavaScript算法源码


const fs = require('fs');

// 读取两行逗号分隔的输入,保留元素在原队列中的排列顺序。
const lines = fs.readFileSync(0, 'utf8').trim().split(/\r?\n/);

const parseArray = (line) =>
    line.split(',').map((part) => Number(part.trim()));

function findSwap(a, b) {
    const sumA = a.reduce((sum, x) => sum + x, 0);
    const sumB = b.reduce((sum, x) => sum + x, 0);

    // 由交换前后的和相等可得:
    // a[i] - b[j] = (sumA - sumB) / 2。
    const diff = sumA - sumB;

    if (diff % 2 !== 0) {
        return [-1, -1];
    }

    const half = diff / 2;

    // 耗时取值是 1~99,直接以耗时作为索引,
    // 记录其在 B 中的原始下标。
    const indexInB = Array(100).fill(-1);

    for (let j = 0; j < b.length; j++) {
        indexInB[b[j]] = j;
    }

    // 逐一计算 A 中任务所需匹配的 B 中耗时。
    // 找到后即可返回原始下标。
    for (let i = 0; i < a.length; i++) {
        const target = a[i] - half;

        if (target >= 1 && target <= 99
                && indexInB[target] !== -1) {
            return [i, indexInB[target]];
        }
    }

    return [-1, -1]; // 题目保证有唯一解。
}

const a = parseArray(lines[0]);
const b = parseArray(lines[1]);

const [i, j] = findSwap(a, b);

console.log(`${i},${j}`);

八、C算法源码


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

// 将以逗号分隔的一行任务耗时写入数组,返回元素个数。
// strtok 修改传入的字符串,因此输入行使用可写字符数组。
int parseArray(char line[], int arr[]) {
    int count = 0;
    char *token = strtok(line, ",");

    while (token != NULL) {
        arr[count++] = atoi(token);
        token = strtok(NULL, ",");
    }

    return count;
}

void findSwap(const int a[], int n, const int b[], int m,
              int *resultI, int *resultJ) {
    int sumA = 0, sumB = 0;

    *resultI = -1;
    *resultJ = -1;

    for (int i = 0; i < n; i++) {
        sumA += a[i];
    }

    for (int j = 0; j < m; j++) {
        sumB += b[j];
    }

    // 交换后两个队列总耗时相等,等价于:
    // a[i] - b[j] = (sumA - sumB) / 2。
    int diff = sumA - sumB;

    if (diff % 2 != 0) {
        return;
    }

    int half = diff / 2;

    // 耗时只可能为 1~99,建立:
    // “任务耗时 -> B 中原始下标”的映射。
    // 初始设置 -1,用于区分“下标为 0”与“不存在”。
    int indexInB[100];

    for (int t = 0; t < 100; t++) {
        indexInB[t] = -1;
    }

    for (int j = 0; j < m; j++) {
        indexInB[b[j]] = j;
    }

    // 遍历 A,O(1) 判断 B 是否存在目标耗时。
    // 无须双层遍历。
    for (int i = 0; i < n; i++) {
        int target = a[i] - half;

        if (target >= 1 && target <= 99
                && indexInB[target] != -1) {
            *resultI = i;
            *resultJ = indexInB[target];
            return;
        }
    }
}

int main(void) {
    char lineA[1024], lineB[1024];
    int a[50], b[50];

    if (fgets(lineA, sizeof(lineA), stdin) == NULL) {
        return 0;
    }

    if (fgets(lineB, sizeof(lineB), stdin) == NULL) {
        return 0;
    }

    int n = parseArray(lineA, a);
    int m = parseArray(lineB, b);

    int i, j;
    findSwap(a, n, b, m, &i, &j);

    printf("%d,%d\n", i, j);

    return 0;
}

九、C++算法源码


#include <array>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

using namespace std;

vector<int> parseArray(const string &line) {
    vector<int> arr;
    string token;
    stringstream ss(line);

    // 以逗号分隔输入。
    // stoi 可以正确处理数字前后的空格。
    while (getline(ss, token, ',')) {
        arr.push_back(stoi(token));
    }

    return arr;
}

pair<int, int> findSwap(const vector<int> &a,
                        const vector<int> &b) {
    int sumA = 0, sumB = 0;

    for (int x : a) {
        sumA += x;
    }

    for (int y : b) {
        sumB += y;
    }

    // 由 sumA-a[i]+b[j] == sumB-b[j]+a[i]
    // 可推导出:
    // a[i] - b[j] = (sumA - sumB) / 2。
    int diff = sumA - sumB;

    if (diff % 2 != 0) {
        return {-1, -1};
    }

    int half = diff / 2;

    // 因为任务耗时范围是 1~99,
    // 采用固定大小数组保存 B 的原始下标。
    // fill(-1) 表示尚无该耗时。
    // 不对任务排序,以免丢失原始下标。
    array<int, 100> indexInB;
    indexInB.fill(-1);

    for (int j = 0; j < (int)b.size(); j++) {
        indexInB[b[j]] = j;
    }

    // 根据 A 的任务耗时直接计算所需的 B 中任务耗时。
    // 使用数组进行 O(1) 查找。
    for (int i = 0; i < (int)a.size(); i++) {
        int target = a[i] - half;

        if (target >= 1 && target <= 99
                && indexInB[target] != -1) {
            return {i, indexInB[target]};
        }
    }

    return {-1, -1}; // 题目保证有唯一解。
}

int main() {
    string lineA, lineB;

    getline(cin, lineA);
    getline(cin, lineB);

    vector<int> a = parseArray(lineA);
    vector<int> b = parseArray(lineB);

    auto answer = findSwap(a, b);

    cout << answer.first << ","
         << answer.second << '\n';

    return 0;
}

十、Go算法源码


package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func parseArray(line string) []int {
    parts := strings.Split(strings.TrimSpace(line), ",")
    arr := make([]int, len(parts))

    // 逐个解析逗号分隔的任务耗时。
    // TrimSpace 可处理数字附近的空格。
    for i, part := range parts {
        arr[i], _ = strconv.Atoi(strings.TrimSpace(part))
    }

    return arr
}

func findSwap(a, b []int) (int, int) {
    sumA, sumB := 0, 0

    // 第一步:分别计算两个队列的总耗时。
    for _, x := range a {
        sumA += x
    }

    for _, y := range b {
        sumB += y
    }

    // 第二步:通过数学公式计算耗时差的一半。
    // 交换后总耗时相等,等价于:
    // a[i] - b[j] = (sumA - sumB) / 2。
    diff := sumA - sumB

    if diff%2 != 0 {
        return -1, -1
    }

    half := diff / 2

    // 第三步:任务耗时仅为 1~99,
    // 用固定数组存放 B 中对应任务的原始下标。
    // 初始化为 -1,表示对应任务耗时不存在。
    var indexInB [100]int

    for i := range indexInB {
        indexInB[i] = -1
    }

    for j, value := range b {
        indexInB[value] = j
    }

    // 第四步:枚举 A 的每个任务,
    // 直接查找 B 中对应耗时。
    // 避免 O(n*m) 双重循环。
    for i, value := range a {
        target := value - half

        if target >= 1 &&
            target <= 99 &&
            indexInB[target] != -1 {
            return i, indexInB[target]
        }
    }

    // 题目保证有唯一解。
    return -1, -1
}

func main() {
    scanner := bufio.NewScanner(os.Stdin)

    if !scanner.Scan() {
        return
    }
    a := parseArray(scanner.Text())

    if !scanner.Scan() {
        return
    }
    b := parseArray(scanner.Text())

    i, j := findSwap(a, b)

    fmt.Printf("%d,%d\n", i, j)
}

🏆下一篇:华为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、付费专栏及课程。

余额充值