101.子数组最大平均数

本文介绍了如何解决寻找给定数组中平均值最大的长度为k的连续子数组问题。首先,通过暴力求解和前缀和的方法解释了基本思路,但这种方法效率较低。接着,提出了滑动窗口优化算法,通过维护两个指针left和right,动态计算窗口内元素的和,降低了空间复杂度到O(1)。整个过程详细阐述了滑动窗口的移动和更新过程,实现了在O(n)的时间复杂度内找到最大平均值子数组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、题目描述

给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数。
在这里插入图片描述

二、解题思路

2.1 方案一:暴力求解和前缀和的方式

这种方法是会超时的,从开始位置枚举出每一个符合条件的连续子数组,然后求平均数,最终保留最大平均数。时间复杂度是O(nk),这里还是给出代码。

class Solution {
    public double findMaxAverage(int[] nums, int k) {
        int maxSum = Integer.MIN_VALUE;
        //遍历整个数组
        for (int i=0; i<nums.length-k+1; i++){
            int sum = 0;
            //遍历连续子数组
            for (int j=i; j<i+k; j++){
                //求和
                sum +=nums[j];
            }
            //求最大和
            maxSum = Math.max(maxSum,sum);
        }
        //返回最大平均数
        return (double)maxSum/k;
    }
}

这种方式存在重复计算,所以时间度比较高,比如下面这个数组,当计算第一个满足条件的连续子数组和第二个连续子数组时存在中间元素的重复计算:
在这里插入图片描述
消除重复计算有两种方案,第一种是前缀和的方式在我的83_一维数组动态和(前缀和)中有,这里也演示一下,首先从左往右,第一个元素是0,第二个1是数组第一个元素的前缀和,第二个13是数组前两个元素的前缀和,第三个8是数组前三个元素的前缀和,以此类推,当我们要计算某个连续子数组的和时,只需要用i+k对应的前缀和减去i的前缀和即可,比如要计算前四个元素的前缀和,用2-0=2,验证一下:1+12-5-6=2。
在这里插入图片描述
代码如下:

class Solution {
    public double findMaxAverage(int[] nums, int k) {
        //用来存储前缀和
        int[] prefixSum = new int[nums.length+1];
        //前缀和数组中第一个元素是0
        prefixSum[0] = 0;
        for (int i=1; i<= nums.length; i++){
            prefixSum[i] = prefixSum[i-1] + nums[i-1];
        }

        int maxSum = Integer.MIN_VALUE;
        for (int i=0; i<nums.length-k+1; i++){
            //计算连续数组的和
            int sum = prefixSum[i+k] - prefixSum[i];
            maxSum = Math.max(maxSum,sum);
        }
        return (double)maxSum/k;
    }
}

时间和空间复杂度都是O(n),使用空间换时间。

2.2 方案二:滑动窗口降低空间复杂度

思想大致是这样的:使用一个变变量来存储每一个前缀和,降低空间复杂度就是用一个变量来代替数组,首先,先用一个变量存储第一个前缀和:
在这里插入图片描述
当我们需要计算第二个前缀和的时候,将滑动窗口向后移动一位,并且加上此时right对应的值:
在这里插入图片描述
同时减掉left对应的值,就求出了第二个连续子数组的和了:
在这里插入图片描述
下面来从头开始详细的模拟下滑动窗口实现的过程

第一步:需要两个指针,初始都是从数组第一个元素开始,同时需要一个变量currWindowSum存储当前元素的变化的累加和,也需要一个maxSum存储最大和:
在这里插入图片描述
在这里插入图片描述
第二步:指针right不断地往后面走,一直到达满足窗口长度k=4的位置,同时right每滑动一个位置,这个位置的元素都需要和当前的currWindowSum求和,并且更新currWindowSum
在这里插入图片描述在这里插入图片描述
right一直向后走,当满足第一个滑动窗口之后,currWindowSum更新为前面元素的和,此时要将currWindowSum的值和maxSum比较
在这里插入图片描述
在这里插入图片描述
第三步:当满足一个窗口的长度之后,先将left往右移动一个,同时right也往右边移动,保证正好是一个窗口的长度:

在这里插入图片描述
此时currWindowSum更新并且和maxSum比较,发现比maxSum大,此时也要更新maxSum:
在这里插入图片描述
第四步:重复第三步的步骤,当right将数组滑动完之后,停止滑动:
在这里插入图片描述
时间复杂度:O(n),只遍历了一遍数组,如果精确一点的话其实是O(2n),因为left和right各遍历了一次。

空间复杂度:O(1)

三、代码演示

class Solution {
    public double findMaxAverage(int[] nums, int k) {
       int maxSum = Integer.MIN_VALUE;

       //定义两个指针
        int left=0, right=0;
        //每一个窗口的和(currWindowSum)
        int windowSum = 0;

        //right指针终止滑动条件
        while(right< nums.length){
            windowSum += nums[right];
            //判断是否满足窗口的条件:达到固定窗口大小
            if (right-left+1 == k){
                //更新maxSum
                maxSum = Math.max(maxSum,windowSum);
                //为了满足下一个窗口的求和,先将此时窗口中元素的和减去left的值
                windowSum -= nums[left];
                //然后left右移动一位,此时windowSum里面只有k-1个元素的和,再加上后移right的值正好是下一个窗口中元素的和
                left++;
            }
            right++;
        }
        return (double)maxSum/k;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值