Given an array consisting of n integers, find the contiguous subarray whose length is greater than or equal to k that has the maximum average value. And you need to output the maximum average value.

Example 1:

Input: [1,12,-5,-6,50,3], k = 4
Output: 12.75
Explanation:
when length is 5, maximum average value is 10.8,
when length is 6, maximum average value is 9.16667.
Thus return 12.75.

Note:

  1. 1 <= k <= n <= 10,000.
  2. Elements of the given array will be in range [-10,000, 10,000].
  3. The answer with the calculation error less than 10-5 will be accepted.

这道题是之前那道 Maximum Average Subarray I 的拓展,那道题说是要找长度为k的子数组的最大平均值,而这道题要找长度大于等于k的子数组的最大平均值。加了个大于k的条件,情况就复杂很多了,之前只要遍历所有长度为k的子数组就行了,现在还要包括所有长度大于k的子数组。我们首先来看 brute force 的方法,就是遍历所有的长度大于等于k的子数组,并计算平均值并更新结 果res。那么先建立累加和数组 sums,结果 res 初始化为前k个数字的平均值,然后让i从 k+1 个数字开始遍历,此时的 sums[i] 就是前 k+1 个数组组成的子数组之和,我们用其平均数来更新结果 res,然后从开头开始去掉数字,直到子数组剩余k个数字为止,再用其平均值来更新解结果 res,通过这种方法,我们就遍历了所有长度大于等于k的子数组。这里需要注意的一点是,更新结果 res 的步骤不能写成 res = min(res, t / (i + 1)) 这种形式,会 TLE,必须要在if中判断 t > res * (i + 1) 才能 accept,写成 t / (i + 1) > res 也不行,必须要用乘法,这也说明了计算机不喜欢算除法吧,参见代码如下:

解法一:

class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
int n = nums.size();
vector<int> sums = nums;
for (int i = ; i < n; ++i) {
sums[i] = sums[i - ] + nums[i];
}
double res = (double)sums[k - ] / k;
for (int i = k; i < n; ++i) {
double t = sums[i];
if (t > res * (i + )) res = t / (i + );
for (int j = i - k; j >= ; --j) {
t = sums[i] - sums[j];
if (t > res * (i - j)) res = t / (i - j);
}
}
return res;
}
};

我们再来看一种 O(n2) 时间复杂度的方法,这里对上面的解法进行了空间上的优化,并没有长度为n数组,而是使用了 preSum 和 sum 两个变量来代替,preSum 初始化为前k个数字之和,sum 初始化为 preSum,结果 res 初始化为前k个数字的平均值,然后从第 k+1 个数字开始遍历,首先 preSum 加上这个数字,sum 更新为 preSum,然后用当前 k+1 个数字的平均值来更新结果 res。和上面的方法一样,我们还是要从开头开始去掉数字,直到子数组剩余k个数字为止,然后用其平均值来更新解结果 res,那么每次就用 sum 减去 nums[j],就可以不断的缩小子数组的长度了,用当前平均值更新结果 res,注意还是要用乘法来判断大小,参见代码如下:

解法二:

class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
double preSum = accumulate(nums.begin(), nums.begin() + k, );
double sum = preSum, res = preSum / k;
for (int i = k; i < nums.size(); ++i) {
preSum += nums[i];
sum = preSum;
if (sum > res * (i + )) res = sum / (i + );
for (int j = ; j <= i - k; ++j) {
sum -= nums[j];
if (sum > res * (i - j)) res = sum / (i - j);
}
}
return res;
}
};

下面来看一种优化时间复杂度到 O(nlg(max - min)) 的解法,其中 max 和 min 分别是数组中的最大值和最小值,是利用了二分搜索法,博主之前写了一篇 LeetCode Binary Search Summary 二分搜索法小结 的博客,这里的二分法应该是小结的第四类,也是最难的那一类,因为判断折半的方向是一个子函数,这里我们没有用子函数,而是写到了一起,可以抽出来成为一个子函数,这一类的特点就是不再是简单的大小比较,而是需要一些复杂的操作来确定折半方向。这里主要借鉴了蔡文森特大神的帖子,所求的最大平均值一定是介于原数组的最大值和最小值之间,所以我们的目标是用二分法来快速的在这个范围内找到要求的最大平均值,初始化 left 为原数组的最小值,right 为原数组的最大值,然后 mid 就是 left 和 right 的中间值,难点就在于如何得到 mid 和要求的最大平均值之间的大小关系,从而判断折半方向。我们想,如果已经算出来了这个最大平均值 maxAvg,那么对于任意一个长度大于等于k的数组,如果让每个数字都减去 maxAvg,那么得到的累加差值一定是小于等于0的,这个不难理解,比如下面这个数组:

[1, 2, 3, 4]   k = 2

我们一眼就可以看出来最大平均值 maxAvg = 3.5,所以任何一个长度大于等于2的子数组每个数字都减去 maxAvg 的差值累加起来都小于等于0,只有产生这个最大平均值的子数组 [3, 4],算出来才正好等于0,其他都是小于0的。那么可以根据这个特点来确定折半方向,我们通过 left 和 right 值算出来的 mid,可以看作是 maxAvg 的一个 candidate,所以就让数组中的每一个数字都减去 mid,然后算差值的累加和,一旦发现累加和大于0了,那么说明 mid 比 maxAvg 小,这样就可以判断方向了。

我们建立一个累加和数组 sums,然后求出原数组中最小值赋给 left,最大值赋给 right,题目中说了误差是 1e-5,所以循环条件就是 right 比 left 大 1e-5,然后算出来 mid,定义一个 minSum 初始化为0,布尔型变量 check,初始化为 false。然后开始遍历数组,先更新累加和数组 sums,注意这个累加和数组不是原始数字的累加,而是它们和 mid 相减的差值累加。我们的目标是找长度大于等于k的子数组的平均值大于 mid,由于每个数组都减去了 mid,那么就转换为找长度大于等于k的子数组的差累积值大于0。建立差值累加数组的意义就在于通过 sums[i] - sums[j] 来快速算出j和i位置中间数字之和,那么只要j和i中间正好差k个数字即可,然后 minSum 就是用来保存j位置之前的子数组差累积的最小值,所以当 i >= k 时,我们用 sums[i - k] 来更新 minSum,这里的 i - k 就是j的位置,然后判断如果 sums[i] - minSum > 0了,说明找到了一段长度大于等k的子数组平均值大于 mid 了,就可以更新 left 为 mid 了,我们标记 check 为 true,并退出循环。在 for 循环外面,当 check 为 true 的时候,left 更新为 mid,否则 right 更新为 mid,参见代码如下:

解法三:

class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
int n = nums.size();
vector<double> sums(n + , );
double left = *min_element(nums.begin(), nums.end());
double right = *max_element(nums.begin(), nums.end());
while (right - left > 1e-) {
double minSum = , mid = left + (right - left) / ;
bool check = false;
for (int i = ; i <= n; ++i) {
sums[i] = sums[i - ] + nums[i - ] - mid;
if (i >= k) {
minSum = min(minSum, sums[i - k]);
}
if (i >= k && sums[i] > minSum) {check = true; break;}
}
if (check) left = mid;
else right = mid;
}
return left;
}
};

下面这种解法对上面的方法优化了空间复杂度 ,使用 preSum 和 sum 来代替数组,思路和上面完全一样,可以参加上面的讲解,注意这里我们的第二个if中是判断 i >= k - 1,而上面的方法是判断 i >= k,这是因为上面的 sums 数组初始化了 n + 1 个元素,注意坐标的转换,而第一个 if 中 i >= k 不变是因为j和i之间就差了k个,所以不需要考虑坐标的转换,参见代码如下:

解法四:

class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
double left = *min_element(nums.begin(), nums.end());
double right = *max_element(nums.begin(), nums.end());
while (right - left > 1e-) {
double minSum = , sum = , preSum = , mid = left + (right - left) / ;
bool check = false;
for (int i = ; i < nums.size(); ++i) {
sum += nums[i] - mid;
if (i >= k) {
preSum += nums[i - k] - mid;
minSum = min(minSum, preSum);
}
if (i >= k - && sum > minSum) {check = true; break;}
}
if (check) left = mid;
else right = mid;
}
return left;
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/644

类似题目:

Maximum Average Subarray I

参考资料:

https://leetcode.com/problems/maximum-average-subarray-ii/

https://leetcode.com/problems/maximum-average-subarray-ii/discuss/105498/c-binary-search-130ms

https://leetcode.com/problems/maximum-average-subarray-ii/discuss/105495/10-line-c-ac-barely-solution-on2

https://leetcode.com/problems/maximum-average-subarray-ii/discuss/105480/Java-solution-O(nlogM)-Binary-search-the-answer

https://leetcode.com/problems/maximum-average-subarray-ii/discuss/105484/C%2B%2B-solution-simple-improvement-to-brute-force-O(nk)

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Maximum Average Subarray II 子数组的最大平均值之二的更多相关文章

  1. [LeetCode] 644. Maximum Average Subarray II 子数组的最大平均值之二

    Given an array consisting of n integers, find the contiguous subarray whose length is greater than o ...

  2. [LeetCode] Maximum Average Subarray I 子数组的最大平均值

    Given an array consisting of n integers, find the contiguous subarray of given length k that has the ...

  3. leetcode 643. Maximum Average Subarray I 子数组最大平均数 I

    一.题目大意 https://leetcode.cn/problems/maximum-average-subarray-i/ 给你一个由 n 个元素组成的整数数组 nums 和一个整数 k . 请你 ...

  4. Leetcode643.Maximum Average Subarray I子数组的最大平均数1

    给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数. 示例 1: 输入: [1,12,-5,-6,50,3], k = 4 输出: 12.75 解释: 最大平均数 (12- ...

  5. leetcode644. Maximum Average Subarray II

    leetcode644. Maximum Average Subarray II 题意: 给定由n个整数组成的数组,找到长度大于或等于k的连续子阵列,其具有最大平均值.您需要输出最大平均值. 思路: ...

  6. 643. Maximum Average Subarray I 最大子数组的平均值

    [抄题]: Given an array consisting of n integers, find the contiguous subarray of given length k that h ...

  7. Maximum Average Subarray II LT644

    Given an array consisting of n integers, find the contiguous subarray whose length is greater than o ...

  8. Maximum Average Subarray II

    Description Given an array with positive and negative numbers, find the maximum average subarray whi ...

  9. LC 644. Maximum Average Subarray II 【lock,hard】

    Given an array consisting of n integers, find the contiguous subarray whose length is greater than o ...

随机推荐

  1. [学习笔记]Javaweb开发视频教程之Tomcat9配置

    参考自北京动力节点的视频教程:https://www.bilibili.com/video/av14548279/?p=1 1.Java XE Java SE 是做电脑上运行的软件. Java EE ...

  2. 测试驱动开发实践2————从testList开始

    内容指引 1.测试之前 2.改造dto层代码 3.改造dao层代码 4.改造service.impl层的list方法 5.通过testList驱动domain领域类的修改 一.测试之前 在" ...

  3. Django+xadmin打造在线教育平台(七)

    十.授课教师 10.1.讲师列表页 拷贝teacher-list.html和teacher-detail.html到templates目录下 先改teacher-list.html,同样继承base. ...

  4. Android 源代码结构

    简介 在使用Andriod SDK进行应用程序开发的时候,我们需要对源代码进行调试,有可能需要进入到某个Android API函数内部进行跟踪调试.但是,如果目标版本的SDK没有关联对应版本的源代码的 ...

  5. 云计算--网络原理与应用--20171123--网络地址转换NAT

    NAT的概述 NAT的配置 实验 一. NAT的概述 NAT(Network address translation,网络地址转换)通过将内部网络的的私有地址翻译成全球唯一的共有网络IP地址,是内部网 ...

  6. Beta冲刺 第一天

    Beta冲刺 第一天 1. 昨天的困难 由于今天还是第一天,所以暂时没有昨天的困难. 2. 今天解决的进度 潘伟靖: 对代码进行了review 1.将某些硬编码改为软编码 2.合并了一些方法,简化代码 ...

  7. ord在python是什么意思?

    >>> help(ord)Help on built-in function ord in module builtins:ord(...) #这是一个函数 ord(c) -> ...

  8. java中<> 的用法

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java语言引 ...

  9. 关于tomcat部署应用的三种方式

    关于tomcat部署应用虽然不是一个经常的操作,因为一旦选择了一种部署方式,我们其他的应用就会不经大脑的使用这种既定模式, 如果不使用这种部署方式,但是对于其他的部署方式不是很清楚的话,很容易抓瞎,所 ...

  10. java的socket通信

    本文讲解如何用java实现网络通信,是一个非常简单的例子,我比较喜欢能够立马看到结果,所以先上代码再讲解具体细节. 服务端: import java.io.BufferedReader; import ...