华为OD机试 - 数组排列求和 - HashSet、分类和迭代(Python/JS/C/C++ 2025 B卷 200分)

在这里插入图片描述

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

专栏导读

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

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

一、题目描述

1到n的n个连续的数字组成一个数组Q,n为3的倍数。

每次按顺序从数组中取出3个元素,去掉这3个元素中的一个最大值和一个最小值,并将剩下的元素累计为S,S初始值为0。

可以通过调整数组中元素的位置改变最终结果,每移动一个元素计为移动一次。

请计算最少移动几次可以使得数组和S最大。

二、输入描述

数组长度n的范围为[3, 600]

数组中数字范围[1, 10000]

数组由一个字符串表示,不同数字元素之间使用空格分隔

三、输出描述

移动次数是一个自然数

无需移动,返回0

四、测试用例

测试用例1

1、输入

1 2 3

2、输出

0

3、说明

只有一个三元组[1,2,3],去掉最大最小值后剩下2,S=2。无需移动。

测试用例2

1、输入

3 1 2 6 4 5

2、输出

0

3、说明

虽然数组已经被打乱,但重排后计算的S值仍然相同,所以不需要移动。

五、解题思路

1、核心思路

最大化 S,意味着每个三元组 (a,b,c) 排序后得到的中间值 y 应该尽可能大。这可以通过将原始数组中第 n/3 到 2n/3-1 大的数作为每个三元组的中间值来实现。为了达到这个目的,每个三元组必须包含一个来自最小的 n/3 个数(P1类),一个来自中间的 n/3 个数(P2类),一个来自最大的 n/3 个数(P3类)。

2、详细解题步骤

  1. 首先,将输入数组的每个元素与其原始索引配对。
  2. 根据元素的值对这些配对进行排序。如果值相同,则可以按原始索引排序以确保一致性(尽管对于本题的逻辑非必需)。
  3. 排序后,前 n/3 个元素被划分为P1类,接下来的 n/3 个元素为P2类,最后的 n/3 个元素为P3类。记录每个原始数组位置上的元素属于哪个类别。
  4. 数组被分成 n/3 个三元组。理想情况下,每个三元组都应包含一个P1、一个P2和一个P3类别的元素。
  5. 遍历原始数组形成的每个三元组 (nums[3k], nums[3k+1], nums[3k+2])。
  6. 查看这三个元素的类别。统计当前三元组中有多少种不同的类别(例如,P1、P2、P3 中出现了几种)。设此数量为 d_k。d_k 的值可以是1、2或3。
  7. d_k 表示这个三元组中,有多少个元素可以“满足”该三元组对不同类别元素的需求。例如,如果三元组是 (P1, P1, P2),它有两种不同类别 (P1, P2),所以 d_k=2。这意味着为了形成 (P1,P2,P3) 的结构,这个三元组内的两个元素(一个P1,一个P2)可以视为“就位”,但还缺少一个P3,并且多了一个P1。那个多余的P1元素就是需要移动的。
  8. 对于一个三元组,需要移动的元素数量是 3 - d_k。
  9. 总的最小移动次数是所有三元组的 (3 - d_k) 之和。这等价于 n - sum(d_k),其中 sum(d_k) 是所有三元组中不同类别数之和。
  10. 这个公式计算的是,总共有多少个元素因为它们所在的当前三元组的类别构成不理想(比如某种类别过多,或者某种类别缺失)而必须被移动。

六、Python算法源码

def solve():
    # 读取一行输入,包含所有数字
    line = input()
    # 按空格分割数字字符串并转换为整数
    nums_str = line.split(" ")
    nums_arr = [int(s) for s in nums_str]
    n = len(nums_arr)

    # 1. 创建元素对象列表,包含值和原始索引
    elements = []
    for i in range(n):
        elements.append({'value': nums_arr[i], 'originalIndex': i})

    # 2. 对元素对象列表进行排序
    # Python的sort是稳定的,如果值相同,原始顺序得以保留
    # 这里我们按值排序,如果值相同,按原始索引(次要排序键,保证唯一性,虽然对此题非必需)
    elements.sort(key=lambda x: (x['value'], x['originalIndex']))

    # 3. 确定每个原始数组元素的类别 (P1, P2, P3)
    # P1 用整数 0 表示, P2 用 1, P3 用 2
    types = [0] * n # types[originalIndex] 表示原始数组中该索引处元素的类别
    partition_size = n // 3 # 每个类别的大小

    for i in range(n):
        current_element = elements[i] # 获取排序后的第 i 个元素
        original_idx = current_element['originalIndex']
        if i < partition_size:
            # 排序后,前 n/3 个元素属于 P1 类
            types[original_idx] = 0 # 标记为 P1 类
        elif i < 2 * partition_size:
            # 排序后,中间 n/3 个元素属于 P2 类
            types[original_idx] = 1 # 标记为 P2 类
        else:
            # 排序后,后 n/3 个元素属于 P3 类
            types[original_idx] = 2 # 标记为 P3 类
    
    # 4. 计算所有三元组中不同类别数量的总和
    total_distinct_types_in_triplets_sum = 0
    num_triplets = n // 3
    # 遍历每个三元组
    for i in range(num_triplets):
        # 当前三元组在原始数组中的起始索引
        triplet_start_index = i * 3
        
        # 获取当前三元组中三个元素的类别
        type_at_pos0 = types[triplet_start_index]
        type_at_pos1 = types[triplet_start_index + 1]
        type_at_pos2 = types[triplet_start_index + 2]

        # 使用 Set 来统计当前三元组中不同类别的数量
        distinct_types_set = set()
        distinct_types_set.add(type_at_pos0)
        distinct_types_set.add(type_at_pos1)
        distinct_types_set.add(type_at_pos2)
        
        # 将当前三元组中不同类别的数量累加到总和中
        total_distinct_types_in_triplets_sum += len(distinct_types_set)

    # 5. 最少移动次数 = n - total_distinct_types_in_triplets_sum
    moves = n - total_distinct_types_in_triplets_sum
    print(moves) # 输出最少移动次数

if __name__ == "__main__":
    solve()

七、JavaScript算法源码

const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal: false // 非交互模式,直接读取一行
});

rl.on('line', (line) => {
    solve(line);
    rl.close(); // 处理完一行后关闭
});

function solve(line) {
    // 读取一行输入,包含所有数字
    const parts = line.split(" "); // 按空格分割数字字符串
    const nums = parts.map(s => parseInt(s)); // 转换为整数
    const n = nums.length; // 数组长度 n

    // 1. 创建元素对象列表,包含值和原始索引
    let elements = [];
    for (let i = 0; i < n; i++) {
        elements.push({ value: nums[i], originalIndex: i });
    }

    // 2. 对元素对象列表进行排序
    // 按值排序,如果值相同,按原始索引(次要排序键)
    elements.sort((a, b) => {
        if (a.value !== b.value) {
            return a.value - b.value;
        }
        return a.originalIndex - b.originalIndex;
    });

    // 3. 确定每个原始数组元素的类别 (P1, P2, P3)
    // P1 用整数 0 表示, P2 用 1, P3 用 2
    let types = new Array(n).fill(0); // types[originalIndex] 表示原始数组中该索引处元素的类别
    const partitionSize = Math.floor(n / 3); // 每个类别的大小

    for (let i = 0; i < n; i++) {
        const currentElement = elements[i]; // 获取排序后的第 i 个元素
        const originalIdx = currentElement.originalIndex;
        if (i < partitionSize) {
            // 排序后,前 n/3 个元素属于 P1 类
            types[originalIdx] = 0; // 标记为 P1 类
        } else if (i < 2 * partitionSize) {
            // 排序后,中间 n/3 个元素属于 P2 类
            types[originalIdx] = 1; // 标记为 P2 类
        } else {
            // 排序后,后 n/3 个元素属于 P3 类
            types[originalIdx] = 2; // 标记为 P3 类
        }
    }

    // 4. 计算所有三元组中不同类别数量的总和
    let totalDistinctTypesInTripletsSum = 0;
    const numTriplets = Math.floor(n / 3);
    // 遍历每个三元组
    for (let i = 0; i < numTriplets; i++) {
        // 当前三元组在原始数组中的起始索引
        const tripletStartIndex = i * 3;
        
        // 获取当前三元组中三个元素的类别
        const typeAtPos0 = types[tripletStartIndex];
        const typeAtPos1 = types[tripletStartIndex + 1];
        const typeAtPos2 = types[tripletStartIndex + 2];

        // 使用 Set 来统计当前三元组中不同类别的数量
        const distinctTypesSet = new Set();
        distinctTypesSet.add(typeAtPos0);
        distinctTypesSet.add(typeAtPos1);
        distinctTypesSet.add(typeAtPos2);
        
        // 将当前三元组中不同类别的数量累加到总和中
        totalDistinctTypesInTripletsSum += distinctTypesSet.size;
    }

    // 5. 最少移动次数 = n - total_distinct_types_in_triplets_sum
    const moves = n - totalDistinctTypesInTripletsSum;
    console.log(moves); // 输出最少移动次数
}

八、C算法源码

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

// 定义一个元素结构体,包含值和原始索引
typedef struct {
    int value;         // 元素的值
    int originalIndex; // 元素在原始数组中的索引
} Element;

// qsort的比较函数
int compareElements(const void *a, const void *b) {
    Element *elemA = (Element *)a;
    Element *elemB = (Element *)b;
    // 首先按值排序
    if (elemA->value != elemB->value) {
        return elemA->value - elemB->value;
    }
    // 如果值相同,则按原始索引排序
    return elemA->originalIndex - elemB->originalIndex;
}

int main() {
    char line[2400]; // 假设一行最多600个数字,每个数字最大5位,加上空格,2400足够
    if (fgets(line, sizeof(line), stdin) == NULL) {
        return 1; // 读取失败
    }

    // 解析输入字符串
    int nums[600]; // 最大长度600
    int n = 0;
    char *token = strtok(line, " \n"); // 按空格或换行符分割
    while (token != NULL && n < 600) {
        nums[n++] = atoi(token);
        token = strtok(NULL, " \n");
    }

    // 1. 创建元素对象数组,包含值和原始索引
    Element elements[n];
    for (int i = 0; i < n; i++) {
        elements[i].value = nums[i];
        elements[i].originalIndex = i;
    }

    // 2. 对元素对象数组进行排序
    qsort(elements, n, sizeof(Element), compareElements);

    // 3. 确定每个原始数组元素的类别 (P1, P2, P3)
    // P1 用整数 0 表示, P2 用 1, P3 用 2
    int types[n]; // types[originalIndex] 表示原始数组中该索引处元素的类别
    int partitionSize = n / 3; // 每个类别的大小

    for (int i = 0; i < n; i++) {
        Element currentElement = elements[i]; // 获取排序后的第 i 个元素
        int originalIdx = currentElement.originalIndex;
        if (i < partitionSize) {
            // 排序后,前 n/3 个元素属于 P1 类
            types[originalIdx] = 0; // 标记为 P1 类
        } else if (i < 2 * partitionSize) {
            // 排序后,中间 n/3 个元素属于 P2 类
            types[originalIdx] = 1; // 标记为 P2 类
        } else {
            // 排序后,后 n/3 个元素属于 P3 类
            types[originalIdx] = 2; // 标记为 P3 类
        }
    }
    
    // 4. 计算所有三元组中不同类别数量的总和
    int totalDistinctTypesInTripletsSum = 0;
    int numTriplets = n / 3;
    // 遍历每个三元组
    for (int i = 0; i < numTriplets; i++) {
        // 当前三元组在原始数组中的起始索引
        int tripletStartIndex = i * 3;
        
        // 获取当前三元组中三个元素的类别
        int typeAtPos0 = types[tripletStartIndex];
        int typeAtPos1 = types[tripletStartIndex + 1];
        int typeAtPos2 = types[tripletStartIndex + 2];

        // 手动模拟Set来统计不同类别数量
        int distinctCount = 0;
        int foundTypes[3] = {0, 0, 0}; // 标记P1, P2, P3是否已找到

        if (!foundTypes[typeAtPos0]) {
            foundTypes[typeAtPos0] = 1;
            distinctCount++;
        }
        if (!foundTypes[typeAtPos1]) {
            foundTypes[typeAtPos1] = 1;
            distinctCount++;
        }
        if (!foundTypes[typeAtPos2]) {
            foundTypes[typeAtPos2] = 1;
            distinctCount++;
        }
        
        // 将当前三元组中不同类别的数量累加到总和中
        totalDistinctTypesInTripletsSum += distinctCount;
    }

    // 5. 最少移动次数 = n - total_distinct_types_in_triplets_sum
    int moves = n - totalDistinctTypesInTripletsSum;
    printf("%d\n", moves); // 输出最少移动次数

    return 0;
}

九、C++算法源码

#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
#include <set>

// 定义一个元素结构体,包含值和原始索引
struct Element {
    int value;         // 元素的值
    int originalIndex; // 元素在原始数组中的索引

    // 构造函数
    Element(int v, int idx) : value(v), originalIndex(idx) {}

    // 排序用的比较运算符
    bool operator<(const Element& other) const {
        if (value != other.value) {
            return value < other.value;
        }
        return originalIndex < other.originalIndex;
    }
};

int main() {
    std::ios_base::sync_with_stdio(false); // 加速C++ IO
    std::cin.tie(NULL);                   // 解除cin和cout的绑定

    std::string line;
    std::getline(std::cin, line); // 读取一行输入

    std::stringstream ss(line);
    std::string segment;
    std::vector<int> nums;
    while (std::getline(ss, segment, ' ')) { // 按空格分割
        if (!segment.empty()) { // 确保段不为空
             nums.push_back(std::stoi(segment));
        }
    }
    int n = nums.size(); // 数组长度 n

    // 1. 创建元素对象列表,包含值和原始索引
    std::vector<Element> elements;
    for (int i = 0; i < n; ++i) {
        elements.emplace_back(nums[i], i);
    }

    // 2. 对元素对象列表进行排序
    std::sort(elements.begin(), elements.end());

    // 3. 确定每个原始数组元素的类别 (P1, P2, P3)
    // P1 用整数 0 表示, P2 用 1, P3 用 2
    std::vector<int> types(n); // types[originalIndex] 表示原始数组中该索引处元素的类别
    int partitionSize = n / 3; // 每个类别的大小

    for (int i = 0; i < n; ++i) {
        const Element& currentElement = elements[i]; // 获取排序后的第 i 个元素
        int originalIdx = currentElement.originalIndex;
        if (i < partitionSize) {
            // 排序后,前 n/3 个元素属于 P1 类
            types[originalIdx] = 0; // 标记为 P1 类
        } else if (i < 2 * partitionSize) {
            // 排序后,中间 n/3 个元素属于 P2 类
            types[originalIdx] = 1; // 标记为 P2 类
        } else {
            // 排序后,后 n/3 个元素属于 P3 类
            types[originalIdx] = 2; // 标记为 P3 类
        }
    }
    
    // 4. 计算所有三元组中不同类别数量的总和
    int totalDistinctTypesInTripletsSum = 0;
    int numTriplets = n / 3;
    // 遍历每个三元组
    for (int i = 0; i < numTriplets; ++i) {
        // 当前三元组在原始数组中的起始索引
        int tripletStartIndex = i * 3;
        
        // 获取当前三元组中三个元素的类别
        int typeAtPos0 = types[tripletStartIndex];
        int typeAtPos1 = types[tripletStartIndex + 1];
        int typeAtPos2 = types[tripletStartIndex + 2];

        // 使用 Set 来统计当前三元组中不同类别的数量
        std::set<int> distinctTypesSet;
        distinctTypesSet.insert(typeAtPos0);
        distinctTypesSet.insert(typeAtPos1);
        distinctTypesSet.insert(typeAtPos2);
        
        // 将当前三元组中不同类别的数量累加到总和中
        totalDistinctTypesInTripletsSum += distinctTypesSet.size();
    }

    // 5. 最少移动次数 = n - total_distinct_types_in_triplets_sum
    int moves = n - totalDistinctTypesInTripletsSum;
    std::cout << moves << std::endl; // 输出最少移动次数

    return 0;
}



🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 2025 B卷 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、付费专栏及课程。

余额充值