給出一個整數數組,有正有負。找到這樣一個子數組,他的長度大於等於 k,且平均值最大。
例1:
輸入:
[1,12,-5,-6,50,3]
3
輸出:
15.667
解釋:
(-6 + 50 + 3) / 3 = 15.667
例2:
輸入:
[5]
1
輸出:
5.000
算法:二分答案
本題看到之後先想到暴力,即枚舉全部可能子數組,時間複雜度O(N^2)會超時,因而咱們考慮更低複雜度的作法,是否有時間複雜度O(NlogN)級別的作法呢?咱們考慮二分答案來解決問題。
算法思路
- 咱們考慮二分平均值,那麼咱們須要一個check函數,能在O(N)複雜度內判斷是否存在一個子數組的平均值大於等於咱們二分出來的平均值
- 對於一個平均數ave,咱們先將nums數組每一個數減去ave,那麼只要存在一個長度大於k的子數組和大於等於0,就說明平均數ave可行,這能夠在O(N)時間內完成
代碼思路
- 設置二分的左右邊界分別爲數組中的最小值和最大值
- 判斷平均值mid是否可行,若可行則說明答案大於等於mid,那麼左邊界等於mid,不然說明答案小於mid,右邊界等於mid
- 將nums數組每一個數減去mid
- 求nums數組的前綴和數組pre
- 設置指針index等於k
- 那麼在nums[0:index]中,長度大於k的子數組,區間和最大爲pre[index - 1] - min{pre[0 : index - k]}
- 將index不斷右移直到指向數組末端,若中間區間和最大值大於等於0,check函數直接返回True,結束後還爲返回值則返回False
- 不斷重複 2 直到 left + 1e-5 == right 退出
- 返回左邊界
複雜度分析
NN表示nums數組長度,max_nums和min_nums分別表示數組中最大值和最小值
實際處理時不須要記錄下整個前綴和數組,只需記錄當前的前綴和和左側最小的前綴和
- 時間複雜度:O(Nlog(max_nums−min_nums))
public class Solution {
* @param nums: an array with positive and negative numbers
* @param k: an integer
* @return: the maximum average
*/
private boolean check(int[] nums, int k, double avg) {
double rightSum = 0, leftSum = 0, minLeftSum = 0;
for (int i = 0; i < k; i++) {
rightSum += nums[i] - avg;
}
for (int i = k; i <= nums.length; i++) {
if (rightSum - minLeftSum >= 0) {
return true;
}
if (i < nums.length) {
rightSum += nums[i] - avg;
leftSum += nums[i - k] - avg;
minLeftSum = Math.min(minLeftSum, leftSum);
}
}
return false;
}
public double maxAverage(int[] nums, int k) {
double left, right, mid;
left = right = nums[0];
for (int i = 0; i < nums.length; i++) {
left = Math.min(nums[i], left);
right = Math.max(nums[i], right);
}
while (left + 1e-5 < right) {
mid = left + (right - left) / 2;
if (check(nums, k, mid)) {
left = mid;
}
else {
right = mid;
}
}
return left;
}
}