题目地址:
https://leetcode.com/problems/maximum-average-subarray-ii/
给定一个长 n n n数组 A A A,再给定一个小于等于 n n n的正整数 k k k,问 A A A里长度大于等于 k k k的平均值最大的子数组的平均值是多少。平均值以浮点数形式返回,有 1 0 − 5 10^{-5} 10−5的误差都算正确。
思路是二分答案,设 A A A的最大值是 M M M,最小值是 m m m,那么答案的范围是 [ m , M ] [m,M] [m,M],对于二分出的答案 x x x,只需要判断一下 A A A中是否存在一个长度大于等于 k k k的子数组的平均值是大于等于 x x x的,等价于问 [ A [ i ] − x ] i [A[i]-x]_i [A[i]−x]i这个数组是否存在长度大于等于 k k k的且和大于等于 0 0 0的子数组。对于这个问题,可以这样做,设新数组 C = [ A [ i ] − x ] i C=[A[i]-x]_i C=[A[i]−x]i,先求一下 C C C的长 k k k的前缀,如果该前缀已经非负了那当然存在,否则枚举子数组右端点 C [ j ] C[j] C[j],只需看一下以 C [ 0 : j − k ] C[0:j-k] C[0:j−k]为右端点的前缀(注意,这里还要把 0 0 0前缀考虑进去)里是否有小于等于 C [ j ] C[j] C[j]的,如果有那就存在。可以用一个变量动态记录前缀里的最小值。代码如下:
public class Solution {
public double findMaxAverage(int[] nums, int k) {
double l, r;
// 算一下二分答案的答案范围
l = r = nums[0];
for (int i = 1; i < nums.length; i++) {
l = Math.min(l, nums[i]);
r = Math.max(r, nums[i]);
}
double eps = 1E-5;
// 开始二分答案
while (l + eps < r) {
double m = l + (r - l) / 2;
// 看一下nums里是否存在长度大于等于k的且平均值大于等于m的子数组
if (check(nums, m, k)) {
l = m;
} else {
r = m;
}
}
return l;
}
private boolean check(int[] A, double t, int k) {
double cur = 0, prev = 0;
// 先算一下A - t这个数组的k前缀
for (int i = 0; i < k; i++) {
cur += A[i] - t;
}
// 如果这个前缀已经非负了,那说明存在,返回true
if (cur >= 0) {
return true;
}
// min存A[0 : i - k]为右端点的前缀里的最小前缀值;
// 注意0也要考虑进去,所以这里初始化为0而不是初始化为正无穷
double min = 0;
for (int i = k; i < A.length; i++) {
cur += A[i] - t;
prev += A[i - k] - t;
min = Math.min(min, prev);
if (cur - min >= 0) {
return true;
}
}
return false;
}
}
时间复杂度 O ( n log ( M − m ) ) O(n\log (M-m)) O(nlog(M−m))。