华为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 分割成若干个组,使得每个组的元素和相等,并且在满足条件的所有分组方式中,找到组数最大的方案。为了实现这一目标,可以采用以下步骤:
- 计算总和: 首先计算数组 nums 的所有元素之和 totalSum。
- 尝试不同的分组数: 从最大的可能分组数 k = m(即每个元素单独成组)开始,依次尝试将数组分割成 k 个组。对于每一个 k,检查 totalSum 是否能被 k 整除,如果不能,则不可能将数组分成 k 个和相等的组,跳过该 k。
- 确定每组的目标和: 如果 totalSum 能被 k 整除,则每个组的目标和为 target = totalSum / k。
- 回溯法尝试分组: 使用回溯法(递归)尝试将数组中的元素分配到 k 个组中,使得每个组的和都等于 target。在分配过程中,采用一些优化策略,如将数组按降序排序,以便尽早发现不可能的情况,从而减少不必要的计算。
- 返回结果: 一旦找到一个可行的分组方案,即可返回当前的 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在线答疑。