【OD机试题笔记】几何平均值最大子数组

题目描述

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

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

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

输入描述

第一行输入为N、L

N表示numbers的大小(1 ≤ N ≤ 100000)
L表示子数组的最小长度(1 ≤ L ≤ N)
之后N行表示numbers中的N个数,每个一行(10^-9 ≤ numbers[i] ≤ 10^9)

输出描述

输出子数组的位置(从0开始计数)和大小,中间用一个空格隔开。

备注
用例保证除几何平均值为最大值的子数组外,其他子数组的几何平均值至少比最大值小10^-10倍

示例1

输入
3 2
2
2
3

输出
1 2

说明
长度至少为2的子数组共三个,分别是{2,2}、{2,3}、{2,2,3},其中{2,3}的几何平均值最大,故输出其位置1和长度2

示例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. 数学转化:将几何平均值最大化问题转化为对数和平均值最大化问题(几何平均值=(x1*x2*...*xk)^(1/k) → 取对数得(lnx1+lnx2+...+lnxk)/k),避免大数乘积溢出;
  2. 二分答案:二分最大几何平均值的对数(上下限为数组元素对数的最小/最大值),通过验证函数判断是否存在长度≥L的子数组满足几何平均值≥当前二分值;
  3. 验证优化:构造转换数组b[i]=ln(num[i])-目标对数,利用前缀和+维护最小前缀和,O(n)验证是否存在符合条件的子数组;
  4. 结果筛选:找到最大几何平均值后,遍历所有可能子数组,筛选出「长度最短、起始位置最靠前」的最优子数组。

浮点数二分技巧

  • 迭代固定次数(80次)保证精度(满足题目1e-10的差值要求);
  • 验证时引入极小误差容忍(1e-12),避免浮点精度误差导致误判。

时空复杂度

  • 时间复杂度:O(N×log(max_gm/eps)),其中 N 为数组长度,log(max_gm/eps) 为二分迭代次数(约80次);验证环节每次O(N),结果筛选环节最坏O(N×L)(实际找到最短长度后立即break,远低于该上界);
  • 空间复杂度:O(N),主要用于存储对数数组、前缀和数组等。

补充说明

  • 二分直接针对「几何平均值的对数」而非原值,减少浮点运算误差;
  • 验证环节通过维护「前j-L个前缀和的最小值」,将暴力验证的O(N×L)优化为O(N),适配N=1e5的规模要求;
  • 结果筛选优先遍历短长度子数组,找到第一个符合条件的即终止,保证「最短长度+最靠前位置」的规则。

##代码

function solve(lines) {
    try {
        let ptr = 0;
        const [N, L] = lines[ptr++].split(' ').map(Number);
        const numbers = [];
        for (let i = 0; i < N; i++) {
            const num = parseFloat(lines[ptr++]);
            if (num <= 0) {
                throw new Error("Non-positive number");
            }
            numbers.push(num);
        }

        // 对数数组
        const logA = numbers.map(x => Math.log(x));
        const logMin = Math.min(...logA);
        const logMax = Math.max(...logA);
        const eps = 1e-12;

        // 判断是否存在长度 >= L 的子数组,其几何平均值 >= exp(targetLog)
        function existsValidSubarray(targetLog) {
            // 构造 b[i] = logA[i] - targetLog
            // 前缀和 prefix[0] = 0, prefix[i] = sum_{0}^{i-1} b[t]
            let prefix = 0;
            let minPrefix = 0; // prefix[0]
            let minIndex = 0;  // 对应 prefix[0] 的位置(虚拟位置 -1)

            // 我们维护一个滑动的最小前缀,只考虑位置 <= current - L
            const prefixHistory = [0]; // prefixHistory[i] = prefix at position i (i from 0 to N)

            for (let i = 0; i < N; i++) {
                prefix += logA[i] - targetLog;
                prefixHistory.push(prefix);

                // 当前右端点是 i(0-based),子数组结束于 i,长度 >= L ⇒ 起始 <= i - L + 1
                // 所以前缀起始点 j 满足 j <= i - L + 1 ⇒ j <= (i + 1) - L
                // prefixHistory[j] 对应子数组 [j, i]
                if (i + 1 >= L) {
                    // 更新 minPrefix 为 min(prefixHistory[0..(i+1)-L])
                    const candidateIdx = (i + 1) - L;
                    if (prefixHistory[candidateIdx] < minPrefix) {
                        minPrefix = prefixHistory[candidateIdx];
                        minIndex = candidateIdx;
                    }

                    if (prefix - minPrefix >= -eps) {
                        return true;
                    }
                }
            }
            return false;
        }

        // 二分最大可能的几何平均值的对数
        let low = logMin;
        let high = logMax;
        for (let iter = 0; iter < 80; iter++) {
            const mid = (low + high) / 2;
            if (existsValidSubarray(mid)) {
                low = mid;
            } else {
                high = mid;
            }
        }

        // 现在 low 是最大几何平均值的对数(近似)
        // 再次扫描,找出所有满足几何平均 >= exp(low) 的子数组中:
        //   - 长度最短
        //   - 若长度相同,起始下标最小(0-based)
        const bestLog = low;
        let bestLen = N + 1;
        let bestStart = -1;

        // 重新计算 b[i] = logA[i] - bestLog
        const b = logA.map(x => x - bestLog);
        const prefix = new Array(N + 1).fill(0);
        for (let i = 0; i < N; i++) {
            prefix[i + 1] = prefix[i] + b[i];
        }

        // 对每个起始点 i,找最小的 j >= i + L 使得 prefix[j] - prefix[i] >= 0
        for (let i = 0; i <= N - L; i++) {
            for (let j = i + L; j <= N; j++) {
                if (prefix[j] - prefix[i] >= -eps) {
                    const len = j - i;
                    if (len < bestLen || (len === bestLen && i < bestStart)) {
                        bestLen = len;
                        bestStart = i;
                    }
                    break; // 找到最短即可
                }
            }
        }

        if (bestStart === -1) {
            // 理论上不会发生
            console.log("[]");
        } else {
            // 输出:起始下标(0-based)和长度
            console.log(`${bestStart} ${bestLen}`);
        }
    } catch (e) {
        console.log("[]");
    }
}

// ===== 测试 =====
let inputs = [
`3 2
2
2
3`,
`10 2
0.2
0.1
0.2
0.2
0.2
0.1
0.2
0.2
0.2
0.2`
];

inputs.forEach(input => {
    solve(input.split('\n'));
});

在寻找一个数组中几何平均值最大子数组时,可以采用以下方法: ### 方法概述 几何平均值的计算公式为: $$ \text{几何平均值} = \left(\prod_{i=1}^{n} a_i\right)^{1/n} $$ 其中,$a_i$ 是子数组中的元素,$n$ 是子数组的长度。目标是找到具有最大几何平均值的连续子数组。 ### 解决思路 1. **对数转换**:由于几何平均值的计算涉及乘积,为了避免数值溢出问题,可以将问题转换为对数形式。对数的加法形式可以避免直接计算乘积带来的数值问题。 $$ \log(\text{几何平均值}) = \frac{1}{n} \sum_{i=1}^{n} \log(a_i) $$ 通过最大化 $\sum_{i=1}^{n} \log(a_i)$,可以间接找到几何平均值最大子数组。 2. **滑动窗口法**:使用滑动窗口技术来遍历数组,计算每个可能的子数组几何平均值,并记录最大值。这种方法的时间复杂度为 $O(n^2)$,适合较小规模的数组。 3. **优化算法**:对于大规模数组,可以使用分治算法或动态规划来优化时间复杂度。例如,结合对数转换和前缀和技术,可以在 $O(n)$ 时间内完成计算。 ### 示例代码 以下是一个使用对数转换和滑动窗口法的实现示例: ```python import math def max_geometric_mean_subarray(arr): n = len(arr) if n == 0: return [] # 将数组转换为对数形式 log_arr = [math.log(x) for x in arr] max_mean = -float('inf') start_index = 0 end_index = 0 for i in range(n): current_sum = 0.0 for j in range(i, n): current_sum += log_arr[j] mean = current_sum / (j - i + 1) if mean > max_mean: max_mean = mean start_index = i end_index = j return arr[start_index:end_index+1] # 测试示例 arr = [1, 2, 3, 4, 5] result = max_geometric_mean_subarray(arr) print("几何平均值最大子数组:", result) ``` ### 分析 - **对数转换**:将原始数组转换为对数形式,避免直接计算乘积可能导致的数值问题。 - **滑动窗口**:通过双重循环遍历所有可能的子数组,计算其几何平均值,并记录最大值。 - **时间复杂度**:该方法的时间复杂度为 $O(n^2)$,适用于较小规模的数组。 ### 相关问题 1. 如何优化几何平均值最大子数组的查找算法以减少时间复杂度? 2. 对数转换是否适用于所有类型的数组?是否存在数值精度问题? 3. 几何平均值与算术平均值子数组查找问题中的区别是什么? 4. 如何处理数组中的零或负数?是否会影响几何平均值的计算? 5. 是否可以结合动态规划或分治算法进一步优化该问题的解决方案?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值