leetcode
leetcode 551 ~ 600
子数组最大平均数 II

子数组最大平均数 II

难度:

标签:

题目描述

代码结果

运行时间: 127 ms, 内存: 17.1 MB


/*
 * Leetcode题目: 子数组最大平均数 II
 * 题目描述: 给定一个包含 n 个整数的数组,找出平均值最大且长度至少为 k 的连续子数组,并输出该最大平均值。
 * 题目思路:
 * 1. 使用二分查找法来解决该问题。
 * 2. 首先确定最大平均值的上下界:最大值设为数组的最大值,最小值设为数组的最小值。
 * 3. 使用二分法不断缩小平均值的范围,并在每一步验证当前平均值是否可行。
 * 4. 验证方法为:遍历数组计算前缀和,并判断是否存在长度至少为 k 的子数组,其平均值大于当前的候选平均值。
 */
import java.util.stream.*;
public class MaxAverageSubarrayStream {
    public double findMaxAverage(int[] nums, int k) {
        double left = IntStream.of(nums).min().orElse(Integer.MIN_VALUE);
        double right = IntStream.of(nums).max().orElse(Integer.MAX_VALUE);
        while (right - left > 1e-5) {
            double mid = left + (right - left) / 2;
            if (canFindLargerAverage(nums, k, mid)) {
                left = mid;
            } else {
                right = mid;
            }
        }
        return left;
    }
 
    private boolean canFindLargerAverage(int[] nums, int k, double mid) {
        double sum = 0, prev = 0, minSum = 0;
        for (int i = 0; i < k; i++) {
            sum += nums[i] - mid;
        }
        if (sum >= 0) return true;
        for (int i = k; i < nums.length; i++) {
            sum += nums[i] - mid;
            prev += nums[i - k] - mid;
            minSum = Math.min(minSum, prev);
            if (sum >= minSum) return true;
        }
        return false;
    }
}

解释

方法:

此题解采用滑动窗口加上双端队列(deque)的方式来求解问题。首先,通过滑动窗口计算初始大小为k的子数组的平均值。随后,继续滑动窗口并在每一步尝试调整子数组的开始和结束位置,以便找到可能的最大平均值。这一过程中使用了双端队列来维护当前窗口的子数组信息。每次移动时,从前面移除元素并可能从队尾合并较小的平均值子数组,从而不断更新当前的最大平均值。通过队列操作,我们能够有效地计算每一个新窗口的平均值,并比较它与已知的最大平均值。

时间复杂度:

O(N)

空间复杂度:

O(N)

代码细节讲解

🦆
如何确定滑动窗口的起始大小k,并且这个大小对算法的效果有什么影响?
滑动窗口的起始大小k通常是根据题目要求或数据特性确定的。在本题中,k可能是一个给定的初始值,表示要求计算的最小子数组的长度。选择合适的k对算法的效果有显著影响:较小的k可能让算法更频繁地更新最大平均值,而较大的k则可能减少更新次数但增大了每次计算的数据量。初始窗口大小k是平衡算法效率和精度的一个关键因素。
🦆
双端队列中元素合并的条件`stack[-1][1] * c1 >= s1 * stack[-1][0]`是如何确保队尾的平均值最大的?能否提供一个具体的例子说明这种合并的情况?
此条件确保在队列中,较新添加的子数组(平均值较小)与前一个子数组合并时,不会导致平均值下降。具体例子:假设队列末尾有一个子数组平均值为3(总和为6,元素个数为2),即`(2, 6)`。现在新加入的元素为1,形成新子数组`(1, 1)`。按照合并条件`6 * 1 >= 1 * 2`,即`6 >= 2`,条件满足,所以这两个子数组应合并为`(3, 7)`,新的平均值为`7 / 3`,仍然保持了队列中平均值不减的原则。
🦆
在调整窗口大小的循环中`while stack and stack[0][1] * ct <= st * stack[0][0]`,这个条件是如何帮助找到更大平均值的?具体是通过什么逻辑实现的?
这个条件检查当前窗口的总平均值是否大于队列首部的子数组平均值。如果是,移除队首的子数组能提高整体窗口的平均值。例如,如果整体窗口平均值(`st / ct`)高于队首子数组的平均值,通过移除这部分较低平均的子数组,剩余部分的平均值可能增加,从而尝试找到一个更大的平均值。这是通过不断调整窗口大小来优化当前窗口的平均值。
🦆
您提到每个元素最多被操作两次,这是否意味着在某些特定情况下,元素的操作次数可以少于两次?如果是,这种情况通常在何时发生?
是的,元素的操作次数可能少于两次。这通常发生在元素在被加入到窗口后很快就被移除,而没有经过队列中的合并过程。例如,如果一个元素被添加到窗口后,随即因为不满足窗口的平均值提高条件而被移除,那么该元素只被操作了一次。这种情况较少见,通常发生在窗口平均值与新加入元素差异较大时。

相关问题

子数组最大平均数 I

给你一个由 n 个元素组成的整数数组 nums 和一个整数 k

请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

任何误差小于 10-5 的答案都将被视为正确答案。

 

示例 1:

输入:nums = [1,12,-5,-6,50,3], k = 4
输出:12.75
解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75

示例 2:

输入:nums = [5], k = 1
输出:5.00000

 

提示:

  • n == nums.length
  • 1 <= k <= n <= 105
  • -104 <= nums[i] <= 104