??今天遇到了一道題,本來(lái)題的不難的,但是就是因?yàn)榧尤肓撕芏嗟南拗疲瑢?dǎo)致了這個(gè)題比較棘手。
題意
給出一個(gè)整數(shù)數(shù)組,有正有負(fù)。找到這樣一個(gè)子數(shù)組,他的長(zhǎng)度大于等于 k,且
平均值最大。
樣例
給出 nums = [1, 12, -5, -6, 50, 3], k = 3
返回 15.667 // (-6 + 50 + 3) / 3 = 15.667
注意事項(xiàng)
保證數(shù)組的大小 >= k
1.傳統(tǒng)的方法(超時(shí))
??這個(gè)題的傳統(tǒng)非常的容易,這里不便再贅述
public static double maxAverage(int[] nums, int k) {
//記錄當(dāng)前k(或者大于k)位數(shù)字和
double sum = 0;
double max = Integer.MIN_VALUE;
if(nums == null || nums.length == 0 || k == 0){
return 0;
}
for(int j = k; j <= nums.length; j++){
//初始化為0
sum = 0;
for(int i = 0; i < nums.length; i++){
if(i >= j){//當(dāng)i > j時(shí),計(jì)算最大值
max = ((sum * 1.0 / j) > max) ? sum * 1.0 / j : max;
sum -= nums[i - j]; //需要減去當(dāng)前選擇的數(shù)字的第一個(gè),因?yàn)榧磳⒃诩尤胍粋€(gè)
}
//加入一個(gè)數(shù)字
sum += nums[i];
}
max = ((sum * 1.0 / j) > max) ? sum * 1.0 / j : max;
}
return max;
}
2.二分法
??對(duì)的,又是二分法。跟前面的快慢指針一樣,這里也是非常規(guī)的二分法使用。
(1).題意介紹
??這個(gè)題讓我們求的是一個(gè)數(shù)組的子數(shù)組(長(zhǎng)度大于等于k)的最大平均值,記住不是最大值,是最大平均值。因?yàn)榈贸鲎畲笾挡灰欢軌蛲瞥鲎畲蟮钠骄担驗(yàn)槠骄挡粌H受和的影響,還要受個(gè)數(shù)影響。
(2).算法概述
??首先一個(gè)數(shù)組的和的平均值一定在這個(gè)數(shù)組中的最小值和最大值之間。這一點(diǎn)肯定沒(méi)有爭(zhēng)議。
??其次,我們現(xiàn)在要做的就是--二分,在這個(gè)范圍里面,不斷的二分,直到找到我們想要的值,但是又應(yīng)該怎么二分呢?
??我們可以這樣來(lái)假設(shè),假設(shè) 最大值為max,最小值為min,中間值為 mid = (min + max ) /2.0(這里為什么要除以2.0,而不是2呢?那是我們?cè)赱min, max]區(qū)間求得我們想要的那個(gè)平均值,而這個(gè)平均值不可能總是整數(shù),有可能是小數(shù)(double),所以,我們除以2.0而不是2)。假設(shè)nums數(shù)組(nums[0], nums[1], nums[2], nums[3].......),如果存在兩個(gè)下標(biāo)i,j(i > j),長(zhǎng)度i - j >= k,使其(相當(dāng)于是nums數(shù)組的子數(shù)組)平均值大于等于mid的值,那么:
(nums[i] + nums[i + 1] + ...... + nums[j]) / (i - j) >= mid.
??所以,得出我們想要的答案必定在[mid, max]區(qū)間里面;反之,如果不存在這兩個(gè)下標(biāo),也就是所有的子數(shù)組的平均值都小于mid,那么我們求得最終的答案肯定小于mid,所以最終的答案肯定在[min, mid]。
??那么,怎么判斷一個(gè)子數(shù)組(長(zhǎng)度L >= k)和的平均值大于mid呢?
??這個(gè)是我們這個(gè)問(wèn)題的一個(gè)難點(diǎn),我們用數(shù)學(xué)公式來(lái)表達(dá),如圖所示:
??如圖上的結(jié)論,我們還可以假設(shè)s數(shù)組,其中
s[0] = 0;
s[1] = b[0];
s[2] = b[0] + b[1] = s[1] + b[1];
s[3] = b[0] + b[1] + b[2] = s[2] + b[2];
?? 那么b[j]到b[i](i > j)區(qū)間和為s[i + 1] - s[j]。要想找出區(qū)間和(長(zhǎng)度大于等于k)的最大值,等價(jià)于找i,j,滿(mǎn)足i - j >= k并且使得s[i] - s[j]最大。要想求出s[i + 1] - s[j]的最大值,我們固定i,只要s[j]最小的話(huà),那么s[i + 1] - s[j]就最大了。
(3).代碼
public static double maxAverage(int[] nums, int k) {
if (nums == null || nums.length == 0 || k == 0) {
return 0;
}
double max = Integer.MIN_VALUE;
double min = Integer.MAX_VALUE;
for(int i = 0; i < nums.length; i++){
if(nums[i] > max){
max = nums[i];
}
if(nums[i] < min){
min = nums[i];
}
}
while (max - min >= 1e-6) {
double mid = (max + min) / 2.0;
if(binarySearch(nums, mid, k)){//表示最大的平均值在[min, mid]里面
min = mid;
}else{//表示最大的平均值在[mid, max]里面
max = mid;
}
}
return min;
}
private static boolean binarySearch(int[] nums, double mid, int k) {
//用來(lái)記錄區(qū)間和的
double sum[] = new double[nums.length + 1];
double min = 0;
sum[0] = 0;
for (int i = 1; i <= nums.length; i++) {
//計(jì)算每個(gè)點(diǎn)的區(qū)間和
sum[i] = sum[i - 1] + (nums[i - 1] - mid);
//min相當(dāng)于是s[j],不過(guò)這里是一步一步的取小值得出來(lái)的
if (i >= k && sum[i] >= min) {//表示有b數(shù)組的和>=0,則取值范圍變?yōu)閇mid, max]
return true;
} if (i >= k) {
min = Math.min(min, sum[i - k + 1]); //一步一步的取小值
}
}
return false;
}