【问题标题】:Need explanation for algorithm searching minimal large sum搜索最小大和的算法需要解释
【发布时间】:2017-07-26 13:21:21
【问题描述】:

我正在练习解决 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.
 */

这是我在我的 cmets 中找到的关于我不明白的部分的解决方案:

      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;
  }

【问题讨论】:

  • "Lower" 是您可以从该数组中获得的最低大和,这是最大的数组元素,因为无论您做什么,都无法获得低于此的大和。如果您只是将所有元素相加并将所有其他块留空,那么您会得到“上”。这是您可以从该数组中获得的最大总和,这也是您将尝试在算法中最小化的总和。
  • 哦,我现在明白了。块呢?你能解释一下吗?比如 int 块和计算它的方法背后的逻辑是什么?
  • Umm.. 首先你以目标总和,一些“相当不错的价值”作为开始,在你的情况下是(lower + upper)/2,然后尝试看看你是否能够收集K子数组,其中每个数组的总和等于或低于您的目标。如果你成功了,你尝试同样的低目标,如果没有 - 你尝试更高的目标。
  • @ohwelppp 仅供参考,您发布的解决方案仅通过了 Codility 上的示例测试。它未通过所有其他测试。

标签: java arrays algorithm


【解决方案1】:

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

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

现在是搜索部分:在solution() 中,您从边界和中点开始(例如 10)。 在calculateBlockCount 中,您计算​​(count ++ 这样做)如果您的总和最多为 10(您的中点/或calculateBlockCount 中的maxSum),您可以制作多少块。
对于示例 10(在 while 循环中),这是 2 个块,现在代码将这个 (blocks) 返回到 solution。然后它检查是小于还是大于K,这是您想要的块数。如果它小于K 您的mid 点很高,因为您在块中放置了许多数组元素。如果它超过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....

希望这会有所帮助,

Gl 学习编码 :)

【讨论】:

  • Mid = 10 calculateBlockCount 返回 2(源码中实际上是 1,count 必须设置为 1),Mid = 7 calculateBlockCount 返回 3
  • 只是要指出:这不是学习编码,而是用算法解决数学问题。编码是最后也是最简单的部分。
【解决方案2】:

您的解决方案似乎存在一些问题。我重写如下:

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;
}
}

【讨论】:

    【解决方案3】:

    这对我很有帮助,以防其他人发现它有帮助。

    把它想象成一个函数:给定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】:

      根据 anhtuannd 的代码,我使用 Java 8 进行了重构。速度稍慢。谢谢anhtuannd。

      IntSummaryStatistics summary = Arrays.stream(A).summaryStatistics();
          long high = summary.getSum();
          long low = summary.getMax();
      
          long result = 0;
          while (high >= low) {
              long mid = (high + low) / 2;
              AtomicLong blocks = new AtomicLong(1);
              Arrays.stream(A).reduce(0, (acc, val) -> {
                  if (acc + val > mid) {
                      blocks.incrementAndGet();
                      return val;
                  } else {
                      return acc + val;
                  }
              });
              if (blocks.get() > K) {
                  low = mid + 1;
              } else if (blocks.get() <= K) {
                  result = mid;
                  high = mid - 1;
              }
          }
          return (int) result;
      

      【讨论】:

        【解决方案5】:

        我在 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 个块的块的总和施加的最低限制。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多