华为OD机试 - 最大平分数组 - 回溯(Python/JS/C/C++ 2024 E卷 200分)

在这里插入图片描述

华为OD机试 2024E卷题库疯狂收录中,刷题点这里

专栏导读

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

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

一、题目描述

给定一个数组 nums,可以将元素分为若干个组,使得每组和相等,求出满足条件的所有分组中,最大的平均组个数。

二、输入描述

第一行输入一个 m。

接着输入 m 个数,表示此数组。

数据范围:1 <= M <= 50,1 <= nums[i] <= 50。

三、输出描述

最大的平均分组个数。

四、测试用例

测试用例1:

1、输入

7
4 3 2 3 5 2 1

2、输出

4

3、说明

可以等分的情况有:

4个子集 (5), (1,4), (2,3), (2,3)

2个子集 (5,1,4), (2,3,2,3)

最大的平均分组个数为4。

测试用例2:

1、输入

9
5 2 1 5 2 1 5 2 1

2、输出

4

3、说明

可以等分的情况有:

4个子集 (5,1), (5,1), (5,1), (2,2,2)

2个子集 (5,1,5,1), (2,2,5,1)

最大的平均分组个数为4。

五、解题思路

1、回溯法

用于尝试将数组中的元素分配到不同的组中。通过递归地分配每个元素到各个组,并在过程中检查是否满足每个组的目标和 target。

在回溯过程中,采用剪枝策略,如:

如果当前组已经达到了目标和,则跳过该组,尝试下一个组。

如果一个组为空且无法放入当前元素,则停止尝试,以避免重复计算。

回溯法适用于需要尝试所有可能分配方式的问题,尤其是在需要满足特定约束条件(如每组和相等)的情况下。

2、具体步骤

这道题的目标是将给定的数组 nums 分割成若干个组,使得每个组的元素和相等,并且在满足条件的所有分组方式中,找到组数最大的方案。为了实现这一目标,可以采用以下步骤:

  1. 计算总和: 首先计算数组 nums 的所有元素之和 totalSum。
  2. 尝试不同的分组数: 从最大的可能分组数 k = m(即每个元素单独成组)开始,依次尝试将数组分割成 k 个组。对于每一个 k,检查 totalSum 是否能被 k 整除,如果不能,则不可能将数组分成 k 个和相等的组,跳过该 k。
  3. 确定每组的目标和: 如果 totalSum 能被 k 整除,则每个组的目标和为 target = totalSum / k。
  4. 回溯法尝试分组: 使用回溯法(递归)尝试将数组中的元素分配到 k 个组中,使得每个组的和都等于 target。在分配过程中,采用一些优化策略,如将数组按降序排序,以便尽早发现不可能的情况,从而减少不必要的计算。
  5. 返回结果: 一旦找到一个可行的分组方案,即可返回当前的 k 作为最大的分组数。如果遍历完所有可能的 k 仍未找到可行方案,则返回1,即所有元素作为一个组。

六、Python算法源码

# Python版本
# 类名为OdTest,使用scanner方式读取输入,找到最大的平均组个数

import sys
import sys
sys.setrecursionlimit(1000000)  # 增加递归深度限制,以防止递归过深

def main():
    import sys
    input = sys.stdin.read().split()  # 读取所有输入并按空白字符分割
    if not input:
        print(1)  # 如果没有输入,默认输出1
        return
    m = int(input[0])  # 读取数组的长度m
    nums = list(map(int, input[1:m+1]))  # 读取接下来的m个数字并存储在数组nums中
    totalSum = sum(nums)  # 计算数组的总和
    nums.sort(reverse=True)  # 将数组按降序排序,优化后续的回溯过程

    # 从最大的可能分组数k = m开始,尝试向下寻找可行的k
    for k in range(m, 0, -1):
        if totalSum % k != 0:
            continue  # 如果总和不能被k整除,跳过当前k
        target = totalSum // k  # 每个组的目标和
        if nums[0] > target:
            continue  # 如果最大的数字大于目标和,无法分配,跳过当前k
        subsets = [0] * k  # 初始化k个子集的当前和为0

        # 使用回溯方法尝试将数字分配到k个子集中
        if backtrack(nums, 0, subsets, target):
            print(k)  # 如果成功分配,输出当前k
            return
    print(1)  # 如果无法分配,输出1

def backtrack(nums, index, subsets, target):
    if index == len(nums):
        return True  # 所有数字都已成功分配到子集
    current = nums[index]  # 当前正在尝试分配的数字
    for i in range(len(subsets)):
        if subsets[i] + current > target:
            continue  # 如果将当前数字加入子集i后超过目标和,跳过
        if i > 0 and subsets[i] == subsets[i-1]:
            continue  # 如果当前子集和与前一个子集和相同,跳过以避免重复
        subsets[i] += current  # 尝试将当前数字加入子集i
        if backtrack(nums, index + 1, subsets, target):
            return True  # 如果成功分配,返回True
        subsets[i] -= current  # 回溯,移除当前数字
    return False  # 如果无法分配,返回False

if __name__ == "__main__":
    main()

七、JavaScript算法源码

// JavaScript版本
// 类名为OdTest,使用Scanner方式读取输入,找到最大的平均组个数

process.stdin.resume();  // 开始读取标准输入
process.stdin.setEncoding('utf8');  // 设置输入编码为utf8

let input = '';
process.stdin.on('data', function(chunk) {
    input += chunk;  // 持续读取输入数据
});

process.stdin.on('end', function() {
    const tokens = input.trim().split(/\s+/);  // 按空白字符分割输入
    if (tokens.length === 0) {
        console.log(1);  // 如果没有输入,默认输出1
        return;
    }
    const m = parseInt(tokens[0]);  // 读取数组的长度m
    const nums = [];
    for (let i = 1; i <= m; i++) {
        nums.push(parseInt(tokens[i]));  // 读取接下来的m个数字并存储在数组nums中
    }
    const totalSum = nums.reduce((a, b) => a + b, 0);  // 计算数组的总和
    nums.sort((a, b) => b - a);  // 将数组按降序排序,优化后续的回溯过程

    // 从最大的可能分组数k = m开始,尝试向下寻找可行的k
    for (let k = m; k >= 1; k--) {
        if (totalSum % k !== 0) continue;  // 如果总和不能被k整除,跳过当前k
        const target = Math.floor(totalSum / k);  // 每个组的目标和
        if (nums[0] > target) continue;  // 如果最大的数字大于目标和,无法分配,跳过当前k
        const subsets = Array(k).fill(0);  // 初始化k个子集的当前和为0

        // 使用回溯方法尝试将数字分配到k个子集中
        if (backtrack(nums, 0, subsets, target)) {
            console.log(k);  // 如果成功分配,输出当前k
            return;
        }
    }
    console.log(1);  // 如果无法分配,输出1
});

// 回溯函数,尝试将数字分配到子集中
function backtrack(nums, index, subsets, target) {
    if (index === nums.length) {
        return true;  // 所有数字都已成功分配到子集
    }
    const current = nums[index];  // 当前正在尝试分配的数字
    for (let i = 0; i < subsets.length; i++) {
        if (subsets[i] + current > target) continue;  // 如果将当前数字加入子集i后超过目标和,跳过
        if (i > 0 && subsets[i] === subsets[i - 1]) continue;  // 如果当前子集和与前一个子集和相同,跳过以避免重复
        subsets[i] += current;  // 尝试将当前数字加入子集i
        if (backtrack(nums, index + 1, subsets, target)) {
            return true;  // 如果成功分配,返回true
        }
        subsets[i] -= current;  // 回溯,移除当前数字
    }
    return false;  // 如果无法分配,返回false
}

八、C算法源码

// C语言版本
// 类名为OdTest,使用scanf方式读取输入,找到最大的平均组个数

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

// 定义一个比较函数,用于qsort,将数组按降序排序
int compare_desc(const void* a, const void* b) {
    int num1 = *(int*)a;
    int num2 = *(int*)b;
    return num2 - num1;  // 降序排序
}

// 回溯函数,尝试将数字分配到子集中
bool backtrack(int nums[], int m, int index, int subsets[], int k, int target) {
    if (index == m) {
        return true;  // 所有数字都已成功分配到子集
    }
    int current = nums[index];  // 当前正在尝试分配的数字
    for (int i = 0; i < k; i++) {
        if (subsets[i] + current > target) {
            continue;  // 如果将当前数字加入子集i后超过目标和,跳过
        }
        if (i > 0 && subsets[i] == subsets[i-1]) {
            continue;  // 如果当前子集和与前一个子集和相同,跳过以避免重复
        }
        subsets[i] += current;  // 尝试将当前数字加入子集i
        if (backtrack(nums, m, index + 1, subsets, k, target)) {
            return true;  // 如果成功分配,返回true
        }
        subsets[i] -= current;  // 回溯,移除当前数字
    }
    return false;  // 如果无法分配,返回false
}

int main(){
    int m;
    if(scanf("%d", &m)!=1){
        printf("1\n");  // 如果没有输入,默认输出1
        return 0;
    }
    int nums[m];
    for(int i=0;i<m;i++){
        scanf("%d", &nums[i]);  // 读取m个数字并存储在数组nums中
    }
    // 将数组按降序排序,优化后续的回溯过程
    qsort(nums, m, sizeof(int), compare_desc);
    int totalSum =0;
    for(int i=0;i<m;i++) totalSum += nums[i];  // 计算数组的总和

    // 从最大的可能分组数k = m开始,尝试向下寻找可行的k
    for(int k=m; k>=1; k--){
        if(totalSum % k !=0){
            continue;  // 如果总和不能被k整除,跳过当前k
        }
        int target = totalSum /k;  // 每个组的目标和
        if(nums[0] > target){
            continue;  // 如果最大的数字大于目标和,无法分配,跳过当前k
        }
        int subsets[k];
        for(int i=0;i<k;i++) subsets[i]=0;  // 初始化k个子集的当前和为0

        // 使用回溯方法尝试将数字分配到k个子集中
        if(backtrack(nums, m, 0, subsets, k, target)){
            printf("%d\n", k);  // 如果成功分配,输出当前k
            return 0;
        }
    }
    printf("1\n");  // 如果无法分配,输出1
    return 0;
}

九、C++算法源码

// C++版本
// 类名为OdTest,使用cin方式读取输入,找到最大的平均组个数

#include <bits/stdc++.h>
using namespace std;

// 回溯函数,尝试将数字分配到子集中
bool backtrack(vector<int>& nums, int m, int index, vector<int>& subsets, int k, int target) {
    if(index == m){
        return true;  // 所有数字都已成功分配到子集
    }
    int current = nums[index];  // 当前正在尝试分配的数字
    for(int i=0; i<k; i++){
        if(subsets[i] + current > target){
            continue;  // 如果将当前数字加入子集i后超过目标和,跳过
        }
        if(i >0 && subsets[i] == subsets[i-1]){
            continue;  // 如果当前子集和与前一个子集和相同,跳过以避免重复
        }
        subsets[i] += current;  // 尝试将当前数字加入子集i
        if(backtrack(nums, m, index +1, subsets, k, target)){
            return true;  // 如果成功分配,返回true
        }
        subsets[i] -= current;  // 回溯,移除当前数字
    }
    return false;  // 如果无法分配,返回false
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int m;
    cin >> m;  // 读取数组的长度m
    vector<int> nums(m);
    for(int i=0;i<m;i++) cin >> nums[i];  // 读取m个数字并存储在数组nums中
    // 将数组按降序排序,优化后续的回溯过程
    sort(nums.begin(), nums.end(), greater<int>());
    int totalSum = accumulate(nums.begin(), nums.end(), 0);  // 计算数组的总和

    // 从最大的可能分组数k = m开始,尝试向下寻找可行的k
    for(int k=m; k>=1; k--){
        if(totalSum % k !=0){
            continue;  // 如果总和不能被k整除,跳过当前k
        }
        int target = totalSum /k;  // 每个组的目标和
        if(nums[0] > target){
            continue;  // 如果最大的数字大于目标和,无法分配,跳过当前k
        }
        vector<int> subsets(k, 0);  // 初始化k个子集的当前和为0

        // 使用回溯方法尝试将数字分配到k个子集中
        if(backtrack(nums, m, 0, subsets, k, target)){
            cout << k << "\n";  // 如果成功分配,输出当前k
            return 0;
        }
    }
    cout << "1\n";  // 如果无法分配,输出1
    return 0;
}


🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 2024 E卷 200分)

🏆本文收录于,华为OD机试真题(Python/JS/C/C++)

刷的越多,抽中的概率越大,私信哪吒,备注华为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、付费专栏及课程。

余额充值