华为OD机试 2024E卷题库疯狂收录中,刷题点这里
专栏导读
本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。
一、题目描述
从一个长度为N的正整数数组numbers中找出长度至少为L且几何平均值Q最大的子数组,并输出其位置和大小。(K个数的几何平均值为K个数的乘积的K次方根)
若有多个子数组的几何平均值均为最大值Q,则输出长度最小的子数组。
若有多个长度最小的子数组的几何平均值均为最大值,则输出最前面的子数组。
二、输入描述
第1行输入N、L:
N表示numbers的大小(1 <= N <= 100000)
L表示子数组的最小长度(1 <= L <= N)
之后N行表示numbers中的N个数,每行一个(1 <= numbers[i] <= 10^9)
三、输出描述
输出子数组的位置(从0开始计数)和大小,中间用一个空格隔开。
备注
用例保证除几何平均值为最大值的子数组外,其他子数组的几何平均值至少比最大值小10^-10倍。
四、测试用例
测试用例1:
1、输入
3 2
2
2
3
2、输出
1 2
3、说明
长度至少为2的子数组共有三个,分别是{2,2}, {2,3}, {2,3},其中{2,3}的几何平均值最大,故输出其位置1和长度2。
测试用例2:
1、输入
10 2
0.2
0.2
0.1
0.2
0.2
0.2
0.1
0.2
0.2
0.2
2、输出
0 2
3、说明
所有长度为2的子数组及其几何平均值:
索引0-1: [0.2, 0.2],GM = 0.2
索引1-2: [0.2, 0.1],GM ≈ 0.141
索引2-3: [0.1, 0.2],GM ≈ 0.141
索引3-4: [0.2, 0.2],GM = 0.2
索引4-5: [0.2, 0.2],GM = 0.2
索引5-6: [0.2, 0.1],GM ≈ 0.141
索引6-7: [0.1, 0.2],GM ≈ 0.141
索引7-8: [0.2, 0.2],GM = 0.2
索引8-9: [0.2, 0.2],GM = 0.2
最大几何平均值为0.2,对应多个子数组。选择最早的子数组,即从索引0开始的子数组,因此输出0 2。
五、解题思路
1、问题分析
我们需要在一个长度为N的正数数组numbers中找出长度至少为L且几何平均值最大的子数组,并输出其起始位置和长度。如果存在多个满足条件的子数组,我们需要按照以下优先级选择:
- 几何平均值最大的子数组。
- 如果有多个几何平均值相同,选择长度最小的子数组。
- 如果仍有多个,选择最前面的子数组。
由于N的范围较大(1 ≤ N ≤ 100,000),需要设计一个高效的算法。
2、具体步骤:
- 读取输入:读取N和L,接着读取N个正数。
- 对数转换:对每个数取自然对数,存储在一个数组中。
- 前缀和计算:计算对数数组的前缀和,以便快速计算任意子数组的对数和。
- 滑动窗口遍历:遍历所有长度为L的子数组,计算其对数和,记录最大对数和及其起始位置。
- 输出结果:输出起始位置和子数组长度L。
六、Python算法源码
import math
import sys
def main():
# 读取输入
input = sys.stdin.read().split()
index = 0
# 读取数组大小N和子数组最小长度L
N = int(input[index])
index += 1
L = int(input[index])
index += 1
# 初始化数组并计算对数值
log_values = []
for _ in range(N):
num = float(input[index])
index += 1
log_values.append(math.log(num)) # 计算每个数的自然对数
# 计算前缀和数组
prefix_sums = [0.0] * (N + 1)
for i in range(1, N + 1):
prefix_sums[i] = prefix_sums[i - 1] + log_values[i - 1]
# 初始化最大对数平均值及对应子数组的起始位置和长度
max_log_average = -math.inf
result_start_index = 0
result_length = L
# 初始化最小前缀和及其索引
min_prefix = 0.0
min_prefix_index = 0
# 遍历前缀和数组,寻找最大对数平均值的子数组
for i in range(L, N + 1):
# 更新最小前缀和为[0, i - L]区间的最小前缀和
if prefix_sums[i - L] < min_prefix:
min_prefix = prefix_sums[i - L]
min_prefix_index = i - L
# 计算当前对数平均值
current_log_average = (prefix_sums[i] - min_prefix) / L
# 更新最大对数平均值及对应的子数组位置和长度
if current_log_average > max_log_average:
max_log_average = current_log_average
result_start_index = min_prefix_index
result_length = L
elif math.isclose(current_log_average, max_log_average, abs_tol=1e-10):
# 如果对数平均值相同,选择长度更小的子数组
if L < result_length:
result_start_index = min_prefix_index
result_length = L
# 如果长度相同,选择起始位置更靠前的子数组(无需操作,因为遍历顺序已保证)
# 输出结果
print(result_start_index, result_length)
if __name__ == "__main__":
main()
七、JavaScript算法源码
// 引入readline模块以读取输入
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let input = [];
rl.on('line', function(line){
input = input.concat(line.trim().split(' '));
}).on('close', function(){
let index = 0;
// 读取数组大小N和子数组最小长度L
const N = parseInt(input[index++]);
const L = parseInt(input[index++]);
// 初始化数组并计算对数值
const logValues = [];
for(let i = 0; i < N; i++){
const num = parseFloat(input[index++]);
logValues.push(Math.log(num)); // 计算每个数的自然对数
}
// 计算前缀和数组
const prefixSums = new Array(N + 1).fill(0);
for(let i = 1; i <= N; i++){
prefixSums[i] = prefixSums[i - 1] + logValues[i - 1];
}
// 初始化最大对数平均值及对应子数组的起始位置和长度
let maxLogAverage = -Infinity;
let resultStartIndex = 0;
let resultLength = L;
// 初始化最小前缀和及其索引
let minPrefix = 0.0;
let minPrefixIndex = 0;
// 遍历前缀和数组,寻找最大对数平均值的子数组
for(let i = L; i <= N; i++){
// 更新最小前缀和为[0, i - L]区间的最小前缀和
if(prefixSums[i - L] < minPrefix){
minPrefix = prefixSums[i - L];
minPrefixIndex = i - L;
}
// 计算当前对数平均值
const currentLogAverage = (prefixSums[i] - minPrefix) / L;
// 更新最大对数平均值及对应的子数组位置和长度
if(currentLogAverage > maxLogAverage){
maxLogAverage = currentLogAverage;
resultStartIndex = minPrefixIndex;
resultLength = L;
} else if(Math.abs(currentLogAverage - maxLogAverage) < 1e-10){
// 如果对数平均值相同,选择长度更小的子数组
if(L < resultLength){
resultStartIndex = minPrefixIndex;
resultLength = L;
}
// 如果长度相同,选择起始位置更靠前的子数组(无需操作,因为遍历顺序已保证)
}
}
// 输出结果
console.log(resultStartIndex + " " + resultLength);
});
八、C算法源码
#include <stdio.h>
#include <math.h>
#include <float.h>
int main(){
int N, L;
// 读取数组大小N和子数组最小长度L
scanf("%d %d", &N, &L);
double log_values[N];
// 读取数组元素并计算对数值
for(int i = 0; i < N; i++){
double num;
scanf("%lf", &num);
log_values[i] = log(num); // 计算每个数的自然对数
}
// 计算前缀和数组
double prefix_sums[N + 1];
prefix_sums[0] = 0.0;
for(int i = 1; i <= N; i++){
prefix_sums[i] = prefix_sums[i - 1] + log_values[i - 1];
}
// 初始化最大对数平均值及对应子数组的起始位置和长度
double max_log_average = -DBL_MAX;
int result_start_index = 0;
int result_length = L;
// 初始化最小前缀和及其索引
double min_prefix = 0.0;
int min_prefix_index = 0;
// 遍历前缀和数组,寻找最大对数平均值的子数组
for(int i = L; i <= N; i++){
// 更新最小前缀和为[0, i - L]区间的最小前缀和
if(prefix_sums[i - L] < min_prefix){
min_prefix = prefix_sums[i - L];
min_prefix_index = i - L;
}
// 计算当前对数平均值
double current_log_average = (prefix_sums[i] - min_prefix) / L;
// 更新最大对数平均值及对应的子数组位置和长度
if(current_log_average > max_log_average){
max_log_average = current_log_average;
result_start_index = min_prefix_index;
result_length = L;
}
else if(fabs(current_log_average - max_log_average) < 1e-10){
// 如果对数平均值相同,选择长度更小的子数组
if(L < result_length){
result_start_index = min_prefix_index;
result_length = L;
}
// 如果长度相同,选择起始位置更靠前的子数组(无需操作,因为遍历顺序已保证)
}
}
// 输出结果
printf("%d %d\n", result_start_index, result_length);
return 0;
}
九、C++算法源码
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int N, L;
// 读取数组大小N和子数组最小长度L
cin >> N >> L;
vector<double> log_values(N);
// 读取数组元素并计算对数值
for(int i = 0; i < N; i++){
double num;
cin >> num;
log_values[i] = log(num); // 计算每个数的自然对数
}
// 计算前缀和数组
vector<double> prefix_sums(N + 1, 0.0);
for(int i = 1; i <= N; i++){
prefix_sums[i] = prefix_sums[i - 1] + log_values[i - 1];
}
// 初始化最大对数平均值及对应子数组的起始位置和长度
double max_log_average = -1e308; // 接近负无穷
int result_start_index = 0;
int result_length = L;
// 初始化最小前缀和及其索引
double min_prefix = 0.0;
int min_prefix_index = 0;
// 遍历前缀和数组,寻找最大对数平均值的子数组
for(int i = L; i <= N; i++){
// 更新最小前缀和为[0, i - L]区间的最小前缀和
if(prefix_sums[i - L] < min_prefix){
min_prefix = prefix_sums[i - L];
min_prefix_index = i - L;
}
// 计算当前对数平均值
double current_log_average = (prefix_sums[i] - min_prefix) / L;
// 更新最大对数平均值及对应子数组的位置和长度
if(current_log_average > max_log_average){
max_log_average = current_log_average;
result_start_index = min_prefix_index;
result_length = L;
}
else if(abs(current_log_average - max_log_average) < 1e-10){
// 如果对数平均值相同,选择长度更小的子数组
if(L < result_length){
result_start_index = min_prefix_index;
result_length = L;
}
// 如果长度相同,选择起始位置更靠前的子数组(无需操作,因为遍历顺序已保证)
}
}
// 输出结果
cout << result_start_index << " " << result_length << "\n";
return 0;
}
🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 2024 E卷 200分)
🏆本文收录于,华为OD机试真题(Python/JS/C/C++)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。