【问题标题】:How to solve codility minMaxDivision如何解决编码minMaxDivision
【发布时间】:2021-02-17 06:02:26
【问题描述】:

我一直在尝试解决 1H30 的这个代码问题,以及如何使用二分搜索来解决。我找到了答案,但我无法理解其背后的逻辑。知道的人可以告诉我这个答案吗?

这是问题

任务描述

给定整数 K、M 和一个非空的零索引数组 A 由 N 个整数组成。数组的每个元素都不大于 比 M.

你应该把这个数组分成K个连续元素的块。 块的大小是 0 到 N 之间的任意整数。 数组应该属于某个块。

从 X 到 Y 的块之和等于 A[X] + A[X + 1] + ... + A[Y]。 空块之和等于0。

大和是任何块的最大和。

例如,给定整数 K = 3、M = 5 和数组 A 这样 那:

A[0] = 2 A[1] = 1 A[2] = 5 A[3] = 1 A[4] = 2 A[5] = 2
A[6] = 2

数组可以分为例如以下几个块:

[2, 1, 5, 1, 2, 2, 2], [], [] 大和为 15; [2], [1, 5, 1, 2], [2, 2] 的和为 9; [2, 1, 5], [], [1, 2, 2, 2] 与 8 大笔款项; [2, 1], [5, 1], [2, 2, 2] 的和为 6。

目标是尽量减少大额金额。在上面的例子中,6 是 最小的大金额。

写一个函数:

函数解(K, M, A);

给定整数 K、M 和一个非空的零索引数组 A 由 N 个整数组成,返回最小的大和。

例如,给定 K = 3、M = 5 和数组 A 使得:

A[0] = 2 A[1] = 1 A[2] = 5 A[3] = 1 A[4] = 2 A[5] = 2
A[6] = 2

该函数应返回 6,如上所述。

假设:

N 和 K 是 [1..100,000] 范围内的整数; M 是 [0..10,000] 范围内的整数; 数组 A 的每个元素都是 [0..M] 范围内的整数。

这是我可以得到的答案

function solution(K, M, A) {
    var begin = A.reduce((a, v) => (a + v), 0)
    begin = parseInt((begin+K-1)/K, 10);
    var maxA = Math.max(A);
    if (maxA > begin) begin = maxA;

    var end = begin + M + 1;
    var res = 0;

    while(begin <= end) {
        var mid = (begin + end) / 2;
        var sum = 0;
        var block = 1;
        for (var ind in A) {
            var a = A[ind];
            sum += a;
            if (sum > mid) {
                ++block;
                if (block > K) break;
                sum = a;
            }
        }
        if (block > K) {
            begin = mid + 1;
        } else {
            res = mid;
            end = mid - 1;
        }
    }
    return res;
}

【问题讨论】:

    标签: javascript algorithm binary-search-tree


    【解决方案1】:

    我想更详细地解释我已经实现的算法,然后用 C++ 正确实现。

    查找输入数组中的最大元素。我们也可以使用 M,但 M 不一定会出现。较小的数字可能是最大值,所以这是轻微的优化。

    计算输入数组的总和。这将是最大的总和。

    应用二分查找,开始是最大元素,结束是总和。最小最大总和将在此范围内。

    对于每个试验,检查我们是否可以将元素压缩到比请求的块数更少的块中。如果它更少,那没关系,因为我们可以使用空块。如果相等,那也是可以接受的。然而,它更大,那么我们可以得出结论,尝试的最小最大和需要更高,以允许单个块更大以减少块数。

    上面可以观察到一个一般原则,即我们分配块的总和越公平,最大的将成为可能的最小。为此,我们需要将尽可能多的元素压缩到一个单独的块中。

    如果尝试的最小最大和的块数小于预期的块数,那么我们可以尝试稍小的最小最大和,否则我们必须尝试更大一点,直到最终找到最佳数字。

    就运行时复杂性而言,解决方案是O(n * log(N * M)),因为二进制搜索是对数的。在最坏的情况下,总和可以是最大元素 M 的 N 倍,这导致N * M 范围用二分搜索平分。内部迭代将遍历所有元素,因此是 N 次。因此,它是O(N * log(N * M)),相当于O(N * log(N + M)

    int check(vector<int>& A, int largest_sum)                                      
    {                                                                               
      int sum = 0;                                                                  
      int count = 0;                                                                
      for (size_t i = 0; i < A.size(); ++i) {                                       
        const int e = A[i];                                                         
        if ((sum + e) > largest_sum) { sum = 0; ++count; }                          
        sum += e;                                                                   
      }                                                                             
                                                                                    
      return count;                                                                 
    }                                                                               
                                                                                    
    int solution(int K, int /*M*/, vector<int> &A)                                  
    {                                                                               
      int start = *max_element(A.begin(), A.end());                                 
      int end = accumulate(A.begin(), A.end(), 0);                                  
      while (start <= end) {                                                        
        int mid = (start + end) / 2;                                                
        if (check(A, mid) < K) end = mid - 1;                                       
        else start = mid + 1;                                                       
      }                                                                             
      return start;                                                                 
    } 
    

    【讨论】:

      【解决方案2】:

      查看和测试解决方案,它们都没有真正起作用。 我决定花一些时间在这上面,这是我的解决方案(适用于任何具有最高性能的用例)。

      using System;
      using System.Linq;
      class Solution
      {
          public int solution(int K, int M, int[] A)
          {
              int start = Math.Max((int)Math.Ceiling((decimal)A.Sum()/(decimal)K), A.Max());
              int end = A.Sum();
              int result = 0;
              while(start <= end)
              {
                  int dicotomie = (end + start) / 2;
                  if(calculateNbBlocks(dicotomie, A) <= K)
                  {
                      result = dicotomie;
                      end = dicotomie - 1;
                  }
                  else
                      start = dicotomie + 1;
              }
              return result;
          }
      
          public int calculateNbBlocks(int dicotomie, int[] A)
          {
              int nbBlocks = 1;
              int sum = 0;
              for(int i=0; i<A.Length; i++)
              {
                  sum += A[i];
                  if(sum > dicotomie)
                  {
                      sum = A[i];
                      nbBlocks++;
                  }
              }
              return nbBlocks;
          }
      }

      【讨论】:

        【解决方案3】:

        我对代码做了一点改动,这样更清楚,但这里是我的解释:

        /*
        K = numberOfBlocks
        M = maxNumber
        A = array
        */
        
        function solution(numberOfBlocks, maxNumber, array) {
        
            let begin = array.reduce((a, b) => (a + b), 0);  // Calculate total sum of A
            begin = Math.ceil(begin / numberOfBlocks);       // Calculate the mean of each theorethical block
            begin = Math.max(begin, Math.max(...array));     // Set begin to the highest number in array if > than the mean
        
            // In short: begin is now the smallest possible block sum
        
        
            // Calculate largest possible block sum
            let end = begin + maxNumber + 1;
            var result = 0;
        
            while (begin <= end) {
        
                // Calculate the midpoint, which is our result guess
                const midpoint = (begin + end) / 2;
        
                let currentBlockSum = 0;
                let block = 1;
        
                for (let number of array) {
                    currentBlockSum += number;
        
                    // If currentBlockSum > midpoint means that we are
                    // in a different block...
                    if (currentBlockSum > midpoint) {
                        ++block;
        
                        // ...so we reset sum with the current number
                        currentBlockSum = number;
        
                        // but if we are out of blocks, our guess (midpoint) is incorrect
                        // and we will have to adjust it
                        if (block > numberOfBlocks)
                            break;  
                    }
                }
        
        
                // If we are out of blocks
                // it means that our guess (midpoint) is bigger than we thought
                if (block > numberOfBlocks) {
                    begin = midpoint + 1;
                // else, it's smaller
                } else {
                    result = midpoint;
                    end = midpoint - 1;
                }
            }
        
            return result;
        }

        【讨论】:

        • 不,它根本不起作用。它适用于给定的示例,但请查看报告:示例示例测试✔确定展开所有正确性测试▶extreme_single单个元素✘RUNTIME ERROR测试程序以退出代码1终止▶extreme_double单元素和双元素✘RUNTIME ERROR测试程序以退出终止代码 1 ▶extreme_min_max 最大值/最小值 ✘ RUNTIME ERROR 测试程序以退出代码 1 终止 ▶ simple1 简单测试 ✘
        【解决方案4】:

        这是解决方案上的binary search。对于每个候选解决方案,我们迭代整个数组一次,将数组块填充到块在超过候选之前可以达到的最大总和。如果总和无法实现,则尝试较小的总和是没有意义的,因此我们搜索更高可能候选者的空间。如果总和是可以实现的,我们会尽可能地尝试较低候选者的空间。

        【讨论】:

        • “候选人”是指阻止吗?您能否简单地告诉我“开始”变量和 M 在答案中扮演的角色。
        • @bihireboris by "candidate" 我的意思是选择作为最小值-最大值的总和,也就是 solution(K, M, A) 的返回值。
        • 知道了:)。关于“开始”变量和M的作用?你知道为什么“开始”不是从零开始而是按照人们认为的方式初始化吗?
        • @bihireboris begin 是我们可以得到的最小候选总和,尽管我还没有弄清楚其计算背后的逻辑——也许你可以?我看到 M 仅用于计算 end,这是一开始要考虑的最大候选总和。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-27
        • 1970-01-01
        • 2017-10-03
        • 2021-01-09
        • 2018-05-11
        • 1970-01-01
        相关资源
        最近更新 更多