一、题目描述
给定 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;
}
}