几何平均值最大子数组

一、题目描述

从一个长度为N的正数数组numbers中找出长度至少为L且几何平均值最大子数组,并输出其位置和大小。(K个数的几何平均值为K个数的乘积的K次方根)

若有多个子数组的几何平均值均为最大值,则输出长度最小的子数组。

若有多个长度相同的子数组的几何平均值均为最大值,则输出最前面的子数组。

二、输入输出描述

输入描述

  • 第一行:两个整数 N(数组长度)和 L(子数组最小长度), 1≤N≤1000001≤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组成的子数组 |

四、解题思路

  1. 核心思想

利用几何平均值的等价数学转换 + 二分查找逼近最大值 + 滑动窗口高效验证,结合结果优先级排序,找到最优子数组。核心是 "通过数学转换规避大数溢出和高次幂计算,用二分查找缩小最优值范围,用滑动窗口快速验证条件,用排序满足结果优先级"。

  1. 问题本质分析
  • 表层问题:找到长度≥l、几何平均值最大,且满足 "最短长度→最前位置" 优先级的子数组;
  • 深层问题:
  1. 计算障碍问题:直接计算子数组几何平均值会面临大数溢出(元素乘积过大)和高次幂精度损失,无法直接求解,需通过数学转换规避;

  2. 最优值查找问题:几何平均值的取值是连续的,无法枚举所有可能值,二分查找是高效的逼近方法(利用 "是否存在几何平均值≥某个值" 的二值性);

  3. 验证效率问题:若暴力验证每个子数组,时间复杂度为 O (n²),效率低下,滑动窗口可将验证复杂度优化到 O (n);

  4. 结果筛选问题:存在多个最优子数组时,需按 "长度最短→起始最前" 的优先级筛选,排序是直接有效的解决方案。

  5. 核心逻辑

  • 数学等价转换:将 "子数组几何平均值≥avg" 转换为 "子数组元素分别除以 avg 后的乘积≥1",规避大数溢出和高次幂计算;
  • 二分查找框架:
  1. 以数组最小 / 最大值为初始上下界,确定几何平均值的可能范围;
  2. 取区间中点midAvg作为待验证值,调用check方法验证是否存在符合条件的子数组;
  3. 根据验证结果收缩区间(存在则更新下界,不存在则更新上界),直到满足精度要求;
  • 滑动窗口验证:通过维护fact(当前区间乘积)和min_pre_fact(历史最小前缀乘积),快速判断是否存在长度≥l的子数组满足乘积≥1;
  • 结果优先级排序:对符合条件的子数组按 "长度升序→起始位置升序" 排序,选取第一个作为最优解。
  1. 步骤拆解

  2. 初始化二分上下界

    • 遍历数组,找到最小值minAvg和最大值maxAvg,作为几何平均值的初始上下界;
    • 初始化结果集合ans,用于存储符合条件的子数组。
  3. 二分迭代逼近最大几何平均值

    • 进入循环,终止条件为maxAvg - minAvg小于精度阈值(maxAvg / 10^10);
    • 每次迭代重置ans,计算二分中点midAvg
    • 调用check方法验证是否存在长度≥l的子数组,其几何平均值≥midAvg
    • 根据验证结果更新上下界:存在则minAvg=midAvg(下界上移),不存在则maxAvg=midAvg(上界下移)。
  4. 滑动窗口验证条件(check 方法)

    • 计算初始长度为l的子数组[0, l-1]的乘积(元素除以midAvg),若≥1 则记录该子数组并标记flag=true
    • 初始化前缀乘积pre_fact、最小前缀乘积min_pre_fact及其结束位置;
    • 滑动窗口遍历数组(iln-1):
    1. 更新factpre_fact,实现窗口右移;
    2. 更新min_pre_factmin_pre_fact_end,记录历史最小前缀乘积;
    3. fact / min_pre_fact ≥1,说明子数组[min_pre_fact_end+1, i]符合条件,记录该子数组并标记flag=true
    • 返回flag,告知二分迭代是否需要收缩区间。
  5. 结果排序与输出

    • 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;
  }
}
相关推荐
杰克逊的日记12 小时前
规控算法(规划 + 控制算法)
大数据·算法·云计算·it
玉树临风ives12 小时前
atcoder ABC439 题解
c++·算法
2501_9418072612 小时前
在迪拜智能机场场景中构建行李实时调度与高并发航班数据分析平台的工程设计实践经验分享
java·前端·数据库
一 乐12 小时前
餐厅点餐|基于springboot + vue餐厅点餐系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端
这周也會开心12 小时前
JVM-垃圾回收算法
jvm·算法
ss27312 小时前
volatile的可见性、安全发布的秘密与ThreadLocal原理
java·开发语言
学编程就要猛12 小时前
算法:4.长度最小的子数组
算法
小猪配偶儿_oaken12 小时前
SpringBoot实现单号生成功能(Java&若依)
java·spring boot·okhttp
宋情写12 小时前
JavaAI04-RAG
java·人工智能
红豆诗人12 小时前
算法和数据结构--时间复杂度和空间复杂度
数据结构·算法