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