需要解释算法搜索最小的大和

时间:2017-03-06 15:34:52

标签: java arrays algorithm

我将Codility问题作为实践解决,并且无法回答其中一个问题。我在互联网上找到答案,但我不知道这个算法的工作原理。有人可以一步一步地指导我吗? 这是一个问题:

 /*
  You are given integers K, M and a non-empty zero-indexed array A consisting of N integers.
  Every element of the array is not greater than M.
    You should divide this array into K blocks of consecutive elements.
    The size of the block is any integer between 0 and N. Every element of the array should belong to some block.
    The sum of the block from X to Y equals A[X] + A[X + 1] + ... + A[Y]. The sum of empty block equals 0.
    The large sum is the maximal sum of any block.
    For example, you are given integers K = 3, M = 5 and array A such that:
      A[0] = 2
      A[1] = 1
      A[2] = 5
      A[3] = 1
      A[4] = 2
      A[5] = 2
      A[6] = 2
    The array can be divided, for example, into the following blocks:
    [2, 1, 5, 1, 2, 2, 2], [], [] with a large sum of 15;
    [2], [1, 5, 1, 2], [2, 2] with a large sum of 9;
    [2, 1, 5], [], [1, 2, 2, 2] with a large sum of 8;
    [2, 1], [5, 1], [2, 2, 2] with a large sum of 6.
    The goal is to minimize the large sum. In the above example, 6 is the minimal large sum.
    Write a function:
    class Solution { public int solution(int K, int M, int[] A); }
    that, given integers K, M and a non-empty zero-indexed array A consisting of N integers, returns the minimal large sum.
    For example, given K = 3, M = 5 and array A such that:
      A[0] = 2
      A[1] = 1
      A[2] = 5
      A[3] = 1
      A[4] = 2
      A[5] = 2
      A[6] = 2
    the function should return 6, as explained above. Assume that:
    N and K are integers within the range [1..100,000];
    M is an integer within the range [0..10,000];
    each element of array A is an integer within the range [0..M].
    Complexity:
    expected worst-case time complexity is O(N*log(N+M));
    expected worst-case space complexity is O(1), beyond input storage (not counting the storage required for input arguments).
    Elements of input arrays can be modified.
 */

以下是我发现的有关我不理解的部分的评论的解决方案:

      public static int solution(int K, int M, int[] A) {
    int lower = max(A);  // why lower is max?
    int upper = sum(A);  // why upper is sum?
    while (true) {
      int mid = (lower + upper) / 2;
      int blocks = calculateBlockCount(A, mid); // don't I have specified number of blocks? What blocks do? Don't get that.
      if (blocks < K) {
        upper = mid - 1;
      } else if (blocks > K) {
        lower = mid + 1;
      } else {
        return upper;
      }
    }
  }

  private static int calculateBlockCount(int[] array, int maxSum) {
    int count = 0;
    int sum = array[0];
    for (int i = 1; i < array.length; i++) {
      if (sum + array[i] > maxSum) {
        count++;
        sum = array[i];
      } else {
        sum += array[i];
      }
    }
    return count;
  }

  // returns sum of all elements in an array
  private static int sum(int[] input) {
    int sum = 0;
    for (int n : input) {
      sum += n;
    }
    return sum;
  }

  // returns max value in an array
  private static int max(int[] input) {
    int max = -1;
    for (int n : input) {
      if (n > max) {
        max = n;
      }
    }
    return max;
  }

5 个答案:

答案 0 :(得分:6)

所以代码所做的是使用二进制搜索的形式(二进制搜索的工作方式在这里解释得非常好,https://www.topcoder.com/community/data-science/data-science-tutorials/binary-search/。它还使用了一个与你的问题非常相似的例子。)。在哪里搜索每个块需要包含的最小总和。在示例中,您需要将数组划分为3个部分

进行二分查找时,您需要定义2个边界,您可以确定在两者之间可以找到答案。这里,下边界是数组中的最大值(lower)。例如,这是5(这是你将数组分成7个块)。上边界(upper)是15,它是数组中所有元素的总和(这是如果你将数组分成1个块。)

现在出现了搜索部分:在solution()中,你从界限和中点开始(例如10)。 如果您的总和最多为10(您的中间点/或calculateBlockCount中的count ++),则maxSum计算(calculateBlockCount这样做)您可以制作多少块。
对于示例10(在while循环中),这是2个块,现在代码将此(blocks)返回到solution。然后它检查是否小于或大于K,这是您想要的块数。如果它的Kmid小于K,那么因为你在块中添加了许多数组元素。如果它超过mid,则upper = mid-1点太高,并且您在阵列中放置的数组元素太少。 现在检查完之后,它将解空间减半(mid)。 这发生在每个循环中,它将解空间减半,使其快速收敛。

现在,您一直在调整K,直到这会给出输入Mid =10 , calculateBlockCount returns 2 blocks solution. 2 blocks < K so upper -> mid-1 =9, mid -> 7 (lower is 5) Mid =7 , calculateBlockCount returns 2 blocks solution() 2 blocks < K so upper -> mid-1 =6, mid -> 5 (lower is 5, cast to int makes it 5) Mid =5 , calculateBlockCount returns 4 blocks solution() 4 blocks < K so lower -> mid+1 =6, mid -> 6 (lower is 6, upper is 6 Mid =6 , calculateBlockCount returns 3 blocks So the function returns mid =6.... 中的数量块。

所以一步一步地去做:

{{1}}

希望这有帮助,

学习编码:)

答案 1 :(得分:1)

似乎您的解决方案有一些问题。我将其重写如下:

class Solution {
public int solution(int K, int M, int[] A) {
    // write your code in Java SE 8
    int high = sum(A);
    int low = max(A);

    int mid = 0;

    int smallestSum = 0;

    while (high >= low) {
        mid = (high + low) / 2;
        int numberOfBlock = blockCount(mid, A);

        if (numberOfBlock > K) {
            low = mid + 1;
        } else if (numberOfBlock <= K) {
            smallestSum = mid;
            high = mid - 1;
        }

    }
    return smallestSum;
}

public int sum(int[] A) {
    int total = 0;
    for (int i = 0; i < A.length; i++) {
        total += A[i];
    }
    return total;
}

public int max(int[] A) {
    int max = 0;
    for (int i = 0; i < A.length; i++) {
        if (max < A[i]) max = A[i];
    }
    return max;
}

public int blockCount(int max, int[] A) {
    int current = 0;
    int count = 1;
    for (int i = 0; i< A.length; i++) {
        if (current + A[i] > max) {
            current = A[i];
            count++;
        } else {
            current += A[i];
        }
    }
    return count;
}
}

答案 2 :(得分:1)

从anhtuannd的代码中,我使用Java 8进行了重构。它稍微慢一些。谢谢anthuannd。

static String mostFrequentWord(Collection<String> values) {
    return values.stream()
            .collect(Collectors.groupingBy(v -> v, Collectors.counting()))
            .entrySet().stream()
            .max(Map.Entry.comparingByValue())
            .map(Map.Entry::getKey)
            .orElseThrow(() -> new UnsupportedOperationException(
                    "Empty collection as values in multimap"));
}

答案 3 :(得分:0)

万一其他人觉得有帮助,这对我有帮助。

将其视为函数:给定k(块计数),我们得到一些largeSum

此函数的反函数是什么?就是说,给定largeSum,我们得到一个k。此反函数在下面实现。

solution()中,我们不断将largeSum的猜测插入反函数中,直到它返回练习中给出的k

为了加快猜测过程,我们使用二进制搜索。

public class Problem {
    int SLICE_MAX = 100 * 1000 + 1;

    public int solution(int blockCount, int maxElement, int[] array) {
        // maxGuess is determined by looking at what the max possible largeSum could be
        // this happens if all elements are m and the blockCount is 1
        // Math.max is necessary, because blockCount can exceed array.length, 
        // but this shouldn't lower maxGuess
        int maxGuess = (Math.max(array.length / blockCount, array.length)) * maxElement;
        int minGuess = 0;
        return helper(blockCount, array, minGuess, maxGuess);
    }

    private int helper(int targetBlockCount, int[] array, int minGuess, int maxGuess) {
        int guess = minGuess + (maxGuess - minGuess) / 2;
        int resultBlockCount = inverseFunction(array, guess);
        // if resultBlockCount == targetBlockCount this is not necessarily the solution
        // as there might be a lower largeSum, which also satisfies resultBlockCount == targetBlockCount
        if (resultBlockCount <= targetBlockCount) {
            if (minGuess == guess) return guess;
            // even if resultBlockCount == targetBlockCount
            // we keep searching for potential lower largeSum that also satisfies resultBlockCount == targetBlockCount
            // note that the search range below includes 'guess', as this might in fact be the lowest possible solution
            // but we need to check in case there's a lower one
            return helper(targetBlockCount, array, minGuess, guess);
        } else {
            return helper(targetBlockCount, array, guess + 1, maxGuess);
        }
    }

    // think of it as a function: given k (blockCount) we get some largeSum
    // the inverse of the above function is that given largeSum we get a k
    // in solution() we will keep guessing largeSum using binary search until 
    // we hit k given in the exercise
    int inverseFunction(int[] array, int largeSumGuess) {
        int runningSum = 0;
        int blockCount = 1;
        for (int i = 0; i < array.length; i++) {
            int current = array[i];
            if (current > largeSumGuess) return SLICE_MAX;
            if (runningSum + current <= largeSumGuess) {
                runningSum += current;
            } else {
                runningSum = current;
                blockCount++;
            }
        }
        return blockCount;
    }
}

答案 4 :(得分:0)

我用python here写了100%的解决方案。结果为here

记住:您正在搜索的是可能答案的集合,而不是数组A

在给出的示例中,他们正在搜索可能的答案。认为[5]为5是一个块的最小最大值。并将[2,1,5,1,2,2,2] 15作为一个块的最大值。

Mid =(5 + 15)// 2.一次切出10个块的总数不会超过3个。

将10-1设为上限,然后重试(5 + 9)// 2为7。一次切出7个块的总数不会超过3个。

将7-1设为较高,然后重试(5 + 6)// 2为5。一次切出5个区块将总共创建3个以上的区块。

将5 + 1设为较低,然后重试(6 + 6)// 2为5。一次切出6个块的总数不会超过3个。

因此,对一个块的总和施加的最低限制为6,这将允许分成3个块。