Maximum Average Subarray

2018-03-18  本文已影响0人  Star_C

Question

from lintcode

Given an array with positive and negative numbers, find the maximum average of a subarray whose length should be greater or equal to given length k.

Notice
It's guaranteed that the size of the array is greater or equal to k.

Example
Given nums = [1, 12, -5, -6, 50, 3], k = 3

Return 15.667 // (-6 + 50 + 3) / 3 = 15.667

Idea

I hate this question. This is really a challenge of mathematics rather than coding skills. I think I need to write an article to explain the solution. Right now I will just put them into code comments. In short, you try to guess an answer that is close enough to the real answer in terms of precision by binary search.

public class Solution {
    /*
     * @param nums: an array with positive and negative numbers
     * @param k: an integer
     * @return: the maximum average
     */
    public double maxAverage(int[] nums, int k) {
        int n = nums.length;
        double left = Integer.MAX_VALUE, right = Integer.MIN_VALUE;
        // find largest and smallest
        for (int i = 0; i < n; i++) {
            left = Math.min(left, (double)nums[i]);
            right = Math.max(right, (double)nums[i]);
        }

        double[] sumDiff = new double[n + 1];
        sumDiff[0] = 0;

        // When the error is big, keep seaching
        while (right - left > 1e-6) {
            // assume guessMaxAvg is the maximum average
            double guessMaxAvg = (left + right) / 2;

            // GOAL: maintain an array such that
            // sumDiff[i + 1] = sum(nums[0] ... nums[i]) - (len(0 .. i) * guessMaxAvg)
            // where 0 <= i < nums.length
            for (int i = 0; i < n; i++) {
                // math trick: difference of sum - sumAvg == n * (each element - average)
                // and we can add them incrementally
                sumDiff[i + 1] = sumDiff[i] + (nums[i] - guessMaxAvg);
            }

            double minTrunk = 0;
            double diffMax = Integer.MIN_VALUE;

            for (int i = k; i <= n; i++) {
                /**
                 *  explanation:
                 *    since sumDiff[i] represents `difference(sum elements, sum average) from 0 to (i - 1)`
                 *    then how to get `difference(sum elements, sum average) between (i - 1) - k + 1 to (i - 1)` ?
                 *    ans: MINUS `difference(sum elements, sum average) from 0 to (i - 1) - k`, i.e.
                 *    minus sumDiff[((i - 1) - k) + 1] = sumDiff[i - k]
                 *
                 *    So we got sumDiff(position x to y) = sumDiff(position 0 to y) - sumDiff(position 0 to x - 1)
                 *      = sumDiff[y + 1] - sumDiff[x]
                 *    I want to find the largest sumDiff(position x to y)
                 *    That means I want the minimal sumDiff[x].
                 *    For each sumDiff[y + 1], the possible x ranges from 0 to (y + 1) - k so that
                 *    number of elements between x to y is larger equals than k.
                 *    Let's pick the minimum to minus.
                 */
                minTrunk = Math.min(minTrunk, sumDiff[i - k]);
                diffMax = Math.max(diffMax, sumDiff[i] - minTrunk);
            }
            if (diffMax >= 0) {
                /**
                 * That means there exists a segment from x to y,
                 * where the average is larger than our guess answer.
                 * and thus makes the diffMax positive.
                 *
                 * In this case, we re-guess from guessMaxAvg to right
                 */
                left = guessMaxAvg;
            }
            else {
                right = guessMaxAvg;
            }
        }
        /**
         *  The error is small enough, is okay to return either left or right
         */
        return left;
    }
}
上一篇下一篇

猜你喜欢

热点阅读