[leetcode/lintcode 題解] Google 面試題:子數組的最大平均值 II

給出一個整數數組,有正有負。找到這樣一個子數組,他的長度大於等於 k,且平均值最大。
  • 保證數組的大小 >= 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)時間內完成
代碼思路
  1. 設置二分的左右邊界分別爲數組中的最小值和最大值
  2. 判斷平均值mid是否可行,若可行則說明答案大於等於mid,那麼左邊界等於mid,不然說明答案小於mid,右邊界等於mid
  • 如何判斷平均值mid是否可行:
    1. nums數組每一個數減去mid
    2. nums數組的前綴和數組pre
    3. 設置指針index等於k
    4. 那麼在nums[0:index]中,長度大於k的子數組,區間和最大爲pre[index - 1] - min{pre[0 : index - k]}
    5. index不斷右移直到指向數組末端,若中間區間和最大值大於等於0check函數直接返回True,結束後還爲返回值則返回False
  1. 不斷重複 2 直到 left + 1e-5 == right 退出
  2. 返回左邊界
複雜度分析
NN表示nums數組長度,max_nums和min_nums分別表示數組中最大值和最小值
  • 空間複雜度:O(1)
實際處理時不須要記錄下整個前綴和數組,只需記錄當前的前綴和和左側最小的前綴和
  • 時間複雜度: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) {
 
        //rightSum表示當前指向位置的前綴和
        //leftSum表示當前指向位置左側k個位置的前綴和
        //minLeftSum表示左側最小的前綴和
 
        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;
 
            //判斷平均值mid是否可行
            //若可行則說明答案大於等於mid,那麼左邊界等於mid
            //不然說明答案小於mid,右邊界等於mid
 
            if (check(nums, k, mid)) {
                left = mid;
            }
            else {
                right = mid;
            }
        }
        return left;
    }
}
 
更多題解參考:九章官網solution
相關文章
相關標籤/搜索