华为OD机试 2024D卷题库疯狂收录中,刷题点这里
专栏导读
本专栏收录于《华为OD机试(JAVA)真题(D卷+C卷+A卷+B卷)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。
一、题目描述
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) 是所有三元组中不同类别数之和。
- 这个公式计算的是,总共有多少个元素因为它们所在的当前三元组的类别构成不理想(比如某种类别过多,或者某种类别缺失)而必须被移动。
六、Java算法源码
// 定义一个元素类,包含值和原始索引,用于排序后还能追踪原始位置
class Element implements Comparable<Element> {
int value; // 元素的值
int originalIndex; // 元素在原始数组中的索引
public Element(int value, int originalIndex) {
this.value = value;
this.originalIndex = originalIndex;
}
@Override
public int compareTo(Element other) {
// 首先按值排序
if (this.value != other.value) {
return Integer.compare(this.value, other.value);
}
// 如果值相同,则按原始索引排序(确保排序稳定性)
return Integer.compare(this.originalIndex, other.originalIndex);
}
}
public class OdTest02 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String line = sc.nextLine(); // 读取一行输入,包含所有数字
String[] parts = line.split(" "); // 按空格分割数字字符串
int n = parts.length; // 数组长度 n 就是数字的个数
int[] nums = new int[n]; // 存储输入的数字
// 将字符串转换为整数并存入数组
for (int i = 0; i < n; i++) {
nums[i] = Integer.parseInt(parts[i]);
}
// 1. 创建元素对象列表,包含值和原始索引
List<Element> elements = new ArrayList<>();
for (int i = 0; i < n; i++) {
elements.add(new Element(nums[i], i));
}
// 2. 对元素对象列表进行排序
// 排序后,我们可以根据元素的顺序确定其所属的 P1, P2, P3 类别
Collections.sort(elements);
// 3. 确定每个原始数组元素的类别 (P1, P2, P3)
// P1 用整数 0 表示, P2 用 1, P3 用 2
int[] types = new int[n]; // types[originalIndex] 表示原始数组中该索引处元素的类别
int partitionSize = n / 3; // 每个类别的大小 (P1, P2, P3 各有 n/3 个元素)
for (int i = 0; i < n; i++) {
Element currentElement = elements.get(i); // 获取排序后的第 i 个元素
if (i < partitionSize) {
// 排序后,前 n/3 个元素属于 P1 类
types[currentElement.originalIndex] = 0; // 标记为 P1 类
} else if (i < 2 * partitionSize) {
// 排序后,中间 n/3 个元素属于 P2 类
types[currentElement.originalIndex] = 1; // 标记为 P2 类
} else {
// 排序后,后 n/3 个元素属于 P3 类
types[currentElement.originalIndex] = 2; // 标记为 P3 类
}
}
// 4. 计算所有三元组中不同类别数量的总和
int totalDistinctTypesInTripletsSum = 0;
// 遍历每个三元组 (总共有 n/3 个三元组)
for (int i = 0; i < n / 3; i++) {
// 当前三元组在原始数组中的起始索引
int tripletStartIndex = i * 3;
// 获取当前三元组中三个元素的类别
// types 数组是按照原始索引存储的类别信息
int typeAtPos0 = types[tripletStartIndex];
int typeAtPos1 = types[tripletStartIndex + 1];
int typeAtPos2 = types[tripletStartIndex + 2];
// 使用 Set 来统计当前三元组中不同类别的数量
Set<Integer> distinctTypesSet = new HashSet<>();
distinctTypesSet.add(typeAtPos0);
distinctTypesSet.add(typeAtPos1);
distinctTypesSet.add(typeAtPos2);
// 将当前三元组中不同类别的数量累加到总和中
totalDistinctTypesInTripletsSum += distinctTypesSet.size();
}
// 5. 最少移动次数 = n - totalDistinctTypesInTripletsSum
// n 是总元素数量。
// totalDistinctTypesInTripletsSum 表示的是,在不对数组进行任何移动的情况下,
// 所有三元组中,能够“正确就位”(即其类别是该三元组所需要的P1, P2, P3之一)的元素占位数的总和。
// 每个必须移动的元素,都对应着 totalDistinctTypesInTripletsSum 中的一个“缺口”。
// 所以 n 减去这个总和,就得到了必须移动的元素数量。
int moves = n - totalDistinctTypesInTripletsSum;
System.out.println(moves); // 输出最少移动次数
sc.close();
}
}
七、效果展示
1、输入
1 2 3 4 5 6 7 8 9
2、输出
6
3、说明
n=9。n/3=3。 类别:nums[0…2] 是P1, nums[3…5] 是P2, nums[6…8] 是P3。 三元组1: (1,2,3)。类别 (P1,P1,P1)。不同类别数 d_0=1。 三元组2: (4,5,6)。类别 (P2,P2,P2)。不同类别数 d_1=1。 三元组3: (7,8,9)。类别 (P3,P3,P3)。不同类别数 d_2=1。 总不同类别数 = 1 + 1 + 1 = 3。移动次数 = 9 - 3 = 6。 (每个三元组都需要移出2个元素,并移入其他2种类型的元素)。
🏆下一篇:华为OD机试 - 简易内存池 - 逻辑分析(Java 2025 B卷 200分)
🏆本文收录于,华为OD机试(JAVA)真题(D卷+C卷+A卷+B卷)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。