一、题目描述
从一个长度为N的正数数组numbers中找出长度至少为L且几何平均值最大子数组,并输出其位置和大小。(K个数的几何平均值为K个数的乘积的K次方根)
若有多个子数组的几何平均值均为最大值,则输出长度最小的子数组。
若有多个长度相同的子数组的几何平均值均为最大值,则输出最前面的子数组。
二、输入输出描述
输入描述
- 第一行:两个整数
N(数组长度)和L(子数组最小长度),1≤N≤100000,1≤L≤N; - 后续
N行:每行一个数字(正数,范围10^-9 ≤ numbers[i] ≤ 10^9),依次对应数组numbers的元素。
输出描述
- 输出子数组的位置(从0开始计数)和大小,中间用一个空格隔开。
三、示例
|----|------------------------------------------------------------------|
| 输入 | 3 2 2 2 3 |
| 输出 | 1 2 |
| 说明 | 长度至少为2的子数组共三个,分别是{2,2}、{2,3}、{2,2,3},其中{2,3}的几何平均值最大,故输出其位置1和长度2 |
|----|-------------------------------------------------------------------------------------|
| 输入 | 10 2 0.2 0.1 0.2 0.2 0.2 0.1 0.2 0.2 0.2 0.2 |
| 输出 | 2 2 |
| 说明 | 有多个长度至少为2的子数组的几何平均值为0.2,其中长度最短的为2,也有多个,长度为2且几何平均值为0.2的子数组最前面的那个为从第二个数开始的两个0.2组成的子数组 |
四、解题思路
- 核心思想
利用几何平均值的等价数学转换 + 二分查找逼近最大值 + 滑动窗口高效验证,结合结果优先级排序,找到最优子数组。核心是 "通过数学转换规避大数溢出和高次幂计算,用二分查找缩小最优值范围,用滑动窗口快速验证条件,用排序满足结果优先级"。
- 问题本质分析
- 表层问题:找到长度≥
l、几何平均值最大,且满足 "最短长度→最前位置" 优先级的子数组; - 深层问题:
-
计算障碍问题:直接计算子数组几何平均值会面临大数溢出(元素乘积过大)和高次幂精度损失,无法直接求解,需通过数学转换规避;
-
最优值查找问题:几何平均值的取值是连续的,无法枚举所有可能值,二分查找是高效的逼近方法(利用 "是否存在几何平均值≥某个值" 的二值性);
-
验证效率问题:若暴力验证每个子数组,时间复杂度为 O (n²),效率低下,滑动窗口可将验证复杂度优化到 O (n);
-
结果筛选问题:存在多个最优子数组时,需按 "长度最短→起始最前" 的优先级筛选,排序是直接有效的解决方案。
-
核心逻辑
- 数学等价转换:将 "子数组几何平均值≥avg" 转换为 "子数组元素分别除以 avg 后的乘积≥1",规避大数溢出和高次幂计算;
- 二分查找框架:
- 以数组最小 / 最大值为初始上下界,确定几何平均值的可能范围;
- 取区间中点
midAvg作为待验证值,调用check方法验证是否存在符合条件的子数组; - 根据验证结果收缩区间(存在则更新下界,不存在则更新上界),直到满足精度要求;
- 滑动窗口验证:通过维护
fact(当前区间乘积)和min_pre_fact(历史最小前缀乘积),快速判断是否存在长度≥l的子数组满足乘积≥1; - 结果优先级排序:对符合条件的子数组按 "长度升序→起始位置升序" 排序,选取第一个作为最优解。
-
步骤拆解
-
初始化二分上下界
- 遍历数组,找到最小值
minAvg和最大值maxAvg,作为几何平均值的初始上下界; - 初始化结果集合
ans,用于存储符合条件的子数组。
- 遍历数组,找到最小值
-
二分迭代逼近最大几何平均值
- 进入循环,终止条件为
maxAvg - minAvg小于精度阈值(maxAvg / 10^10); - 每次迭代重置
ans,计算二分中点midAvg; - 调用
check方法验证是否存在长度≥l的子数组,其几何平均值≥midAvg; - 根据验证结果更新上下界:存在则
minAvg=midAvg(下界上移),不存在则maxAvg=midAvg(上界下移)。
- 进入循环,终止条件为
-
滑动窗口验证条件(check 方法)
- 计算初始长度为
l的子数组[0, l-1]的乘积(元素除以midAvg),若≥1 则记录该子数组并标记flag=true; - 初始化前缀乘积
pre_fact、最小前缀乘积min_pre_fact及其结束位置; - 滑动窗口遍历数组(
i从l到n-1):
- 更新
fact和pre_fact,实现窗口右移; - 更新
min_pre_fact和min_pre_fact_end,记录历史最小前缀乘积; - 若
fact / min_pre_fact ≥1,说明子数组[min_pre_fact_end+1, i]符合条件,记录该子数组并标记flag=true;
- 返回
flag,告知二分迭代是否需要收缩区间。
- 计算初始长度为
-
结果排序与输出
- 对
ans集合中的子数组按 "长度升序→起始位置升序" 排序; - 选取排序后的第一个子数组,输出其起始位置和长度。
- 对
五、代码实现
java
import java.util.ArrayList;
import java.util.Objects;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int l = sc.nextInt();
double[] numbers = new double[n];
for (int i = 0; i < n; i++) {
numbers[i] = sc.nextDouble();
}
System.out.println(getResult(n, l, numbers));
}
public static String getResult(int n, int l, double[] numbers) {
double minAvg = Integer.MAX_VALUE;
double maxAvg = Integer.MIN_VALUE;
for (double num : numbers) {
minAvg = Math.min(num, minAvg);
maxAvg = Math.max(num, maxAvg);
}
// double diff = maxAvg / Math.pow(10, 10);
ArrayList<Integer[]> ans = new ArrayList<>();
// 其他子数组的几何平均值至少比最大值小10^-10倍
while (maxAvg - minAvg >= maxAvg / Math.pow(10, 10)) {
// 不保留历史avg对应的ans,只保留最后一个avg,即最大avg的ans
ans = new ArrayList<>();
double midAvg = (minAvg + maxAvg) / 2;
if (check(n, l, numbers, midAvg, ans)) {
minAvg = midAvg;
} else {
maxAvg = midAvg;
}
}
// 若有多个子数组的几何平均值均为最大值,则输出长度最小的子数组。
// 若有多个长度相同的子数组的几何平均值均为最大值,则输出最前面的子数组。
ans.sort((a, b) -> !Objects.equals(a[1], b[1]) ? a[1] - b[1] : a[0] - b[0]);
Integer[] tmp = ans.get(0);
return tmp[0] + " " + tmp[1];
}
public static boolean check(
int n, int l, double[] numbers, double avg, ArrayList<Integer[]> ans) {
// 该flag为True表示avg取小了,为False表示avg取大了,默认为False
boolean flag = false;
double fact = 1;
for (int i = 0; i < l; i++) {
fact *= numbers[i] / avg;
}
if (fact >= 1) {
flag = true;
// ans的元素含义:[目标子数组起始位置,目标子数组长度]
ans.add(new Integer[] {0, l});
}
double pre_fact = 1;
double min_pre_fact = Integer.MAX_VALUE;
int min_pre_fact_end = 0;
for (int i = l; i < n; i++) {
fact *= numbers[i] / avg; // 对应0~i区间
pre_fact *= numbers[i - l] / avg; // 对应0~i-l区间
if (pre_fact < min_pre_fact) {
min_pre_fact = pre_fact; // 对应0~i-l区间内 几何平均值最小的子数列
min_pre_fact_end = i - l;
}
if (fact / min_pre_fact >= 1) {
flag = true;
// ans的元素含义:[目标子数组起始位置,目标子数组长度]
ans.add(new Integer[] {min_pre_fact_end + 1, i - min_pre_fact_end});
}
}
return flag;
}
}