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

在这里插入图片描述

华为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、详细解题步骤

  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. 这个公式计算的是,总共有多少个元素因为它们所在的当前三元组的类别构成不理想(比如某种类别过多,或者某种类别缺失)而必须被移动。

六、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在线答疑。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哪 吒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值