这道来自华为OD机考双机位C卷的编程题,考察的是在给定约束条件下寻找最优子数组的算法设计能力。题目要求我们从一个正整数数组中找出长度不小于L的连续子数组,使得该子数组的几何平均值最大,并返回这个最大几何平均值。
几何平均值与算术平均值的区别在于计算方式:对于包含n个正数的数组,几何平均值是这些数乘积的n次方根。这种计算方式在金融、统计学等领域有广泛应用,比如计算复合增长率时就需要用到几何平均。
最直观的解法是暴力枚举所有长度≥L的子数组,计算每个子数组的几何平均值,然后取最大值。但这种方法的时间复杂度为O(n²),当n较大时(比如n=10^5)会非常低效。
考虑到几何平均值的特性,我们可以利用数学转换来优化计算:
基于上述观察,我们可以采用以下优化策略:
这种组合算法可以将时间复杂度降低到O(n log(max_val)),其中max_val是数组中的最大值。
java复制public double findMaxGeometricMean(int[] nums, int L) {
// 输入校验
if (nums == null || nums.length == 0 || L <= 0 || L > nums.length) {
return 0;
}
double left = 1, right = 0;
for (int num : nums) {
right = Math.max(right, num);
}
// 处理精度问题
double precision = 1e-6;
double result = 0;
while (right - left > precision) {
double mid = left + (right - left) / 2;
if (check(nums, L, mid)) {
result = mid;
left = mid;
} else {
right = mid;
}
}
return result;
}
java复制private boolean check(int[] nums, int L, double target) {
double[] prefixSum = new double[nums.length + 1];
double[] prefixMin = new double[nums.length + 1];
for (int i = 1; i <= nums.length; i++) {
// 关键转换:使用对数将乘积转为求和
prefixSum[i] = prefixSum[i - 1] + Math.log(nums[i - 1]) - Math.log(target);
prefixMin[i] = Math.min(prefixMin[i - 1], prefixSum[i]);
if (i >= L && prefixSum[i] - prefixMin[i - L] >= 0) {
return true;
}
}
return false;
}
由于几何平均值可能是浮点数,我们需要设置合理的精度阈值。通常使用1e-6就能满足大多数情况的需求。过高的精度会导致不必要的计算,而过低的精度可能无法通过测试用例。
通过维护前缀和数组prefixSum和前缀最小值数组prefixMin,我们可以在O(1)时间内判断任意子数组是否满足条件。这是滑动窗口算法的典型应用。
使用对数转换有两个主要优势:
java复制@Test
public void testFindMaxGeometricMean() {
Solution solution = new Solution();
// 基础测试
int[] nums1 = {2, 5, 3, 8, 1};
assertEquals(5.0, solution.findMaxGeometricMean(nums1, 2), 1e-6);
// 全相同元素
int[] nums2 = {4, 4, 4, 4};
assertEquals(4.0, solution.findMaxGeometricMean(nums2, 3), 1e-6);
// 边界情况
int[] nums3 = {10};
assertEquals(10.0, solution.findMaxGeometricMean(nums3, 1), 1e-6);
}
对于大规模数据(如n=10^5),应验证算法在合理时间内完成计算。可以使用随机生成的数组进行压力测试。
当测试用例不通过时,首先检查是否因精度设置不当导致。可以尝试:
特别注意以下边界情况:
如果遇到超时问题,可以考虑:
如果题目变为加权几何平均值,只需在计算前缀和时加入权重因子:
java复制prefixSum[i] = prefixSum[i - 1] + weight[i] * (Math.log(nums[i - 1]) - Math.log(target));
对于矩阵形式的输入,可以将其展平为一维数组处理,或者开发专门针对二维数组的解决方案。
如果需要处理数据流,可以维护一个滑动窗口的数据结构,实时更新几何平均值。