【问题标题】:Split number into sum components将数字拆分为总和分量
【发布时间】:2011-10-16 23:42:18
【问题描述】:

是否有一种有效的算法将一个数字分成N 小节,以便数字的总和与原始数字相加,并具有最小值?例如,如果我想将 50 分成 7 个小节,并且最小为 2,我可以使用 10,5,8,2,3,5,17(以及任何其他数量的组合)。我想将数字保持为整数,并且相对随机,但我不确定如何有效地生成与原始数字相加并且不包括低于给定最小值的数字。有什么建议吗?

编辑 - 只是为了复制/粘贴我的评论,整数不必是唯一的,但我想避免所有整数的大小相同(例如,50 分成 10 个相同的大小)。

【问题讨论】:

  • 子集总和:给定一组数字,找到总和为特定数字的子集。你的问题:给定一个数字,找到它对应的子集。我敢打赌你在 NP 完全域中:)
  • 您是否希望所有整数都是唯一的?
  • @Skoder - 为什么不“随机化” :) 这将非常容易,你会得到你想要的!如果您需要“切片”为 5 块 - 只需随机选择 4 个递增数字直到上限!
  • @Nupul 我认为矩阵 [N,Set] 就足够了。在 OP 的示例中,矩阵 [7,50],在您的情况下为 [100,23]。作为一个问题约束,您将忽略数字
  • 我在这个问题中看不到任何与 NP 相关的内容。您甚至可以找到一个(复杂的)组合公式Spl(X,N,M)=..ComplexFormula..,它为您提供了多少种方法可以将一个数字X 拆分为N 至少具有M 的小节。例如,Spl(50,7,7) 将是 =1

标签: performance algorithm math numbers sum


【解决方案1】:

这是一个算法:

  1. N 除以m,其中N 是您的号码,m 是小节的数量。
  2. 将结果向下舍入到最接近的值,并将该值分配给所有小节。
  3. 向每个小节添加一个,直到值加起来为N。此时,如果 N 为 50,m 为 7,则您将有 8、7、7、7、7、7、7
  4. 从 0 迭代到 m-1,步长为 2,并在 -(currentValue-base)currentValue-base 之间添加一个随机数。将该数字的倒数添加到其相邻存储桶中。如果您有奇数个存储桶,则在最后一个存储桶上,不要将该数字的倒数添加到其相邻存储桶中,而是以类似于上述步骤 2 和 3 的分布式方式将其添加到所有其他存储桶中。

性能: 第1步是O(1),第2、3、4步是O(m),所以总体上是O(m)

【讨论】:

  • @Skoder:根据您的要求,似乎只是逐步选择随机数达到指定的最大值应该可以满足您的需求。您想将块拆分为组成部分是主要意图,而不是数字是否可以精确地加起来为特定数字
【解决方案2】:

您可以通过从数字中减去最小乘以 N、生成 N 个小节并添加最小值来轻松消除最小值的要求。在您的示例中,问题简化为将 36 拆分为 7 个整数,并且您给出了拆分 8、3、6、0、1、3、15。

解决方案的其余部分取决于“相对随机”要求的性质。对于一些最小随机性,请考虑在 0 和未拆分部分之间顺序选择数字(例如,首先在 0 和 36 之间,获得 8,然后在 0 和 28 之间,获得 3,依此类推 7 次)。如果这还不够,您需要先定义随机性。

【讨论】:

  • “随机性”是指结果并不总是相同的。因此,如果将 50 拆分 10 次,则不会总是 10,10,10,10,10。
  • @Skoder:好吧,那么我的“最小随机性”解决方案就足够了,您的解决方案在 O(k) 中。不过,第一部分往往是最大的。
  • 您应该在完成上述步骤后随机播放列表。
  • @Fantius:这会有所帮助,但仍然会导致分布不均。
【解决方案3】:

这是一个伪随机解[注意,解可能有偏差,但会相对随机]。

input:
n - the number we should sum up to
k - the number of 'parts'
m - minimum

(1) split n into k numbers: x1,x2,...,xk such that x1+...+xk = n, and the numbers 
    are closest possible to each other [in other words, x1 = x2 = ... = n/k where 
    possible, the end might vary at atmost +-1.]
(2) for each number xi from i=1 to k-1:
       temp <- rand(m,xi)
       spread x - temp evenly among xi+1,...,xk
       xi <- temp
(3) shuffle the resulting list.

关于第 1 部分,例如:对于n=50, k = 7,您将设置: x1=x2=...=x6=7,x7=8,用线性时间计算和填充这样的列表没有问题。

性能:

如前所述,step1 是 O(k)。

Step2,简单的实现是O(k^2),但是由于你平均分配temp-xi的结果,所以有O(k)的实现,只需要存储和修改delta。

Step3 只是一个简单的 shuffle,O(k)

整体性能:O(k) 增量实现 step2

【讨论】:

    【解决方案4】:

    嗯,我想出了一些“只是为了好玩”的东西。

    它从minimum 递增到number,并使用模数和随机数填充N 部分的数组。

    See the jsFiddle here.

    如果此号码的部分过多,它将无法按预期工作。 (即number &lt; N(N+1)/2

    【讨论】:

      【解决方案5】:

      这是一个创建请求的数字重新分区的代码的 java 示例。它是递归方法,我们将问题分解为 2 个子问题:如果我们想将一个数字分解为 n 个篮子中的组件的总和,那么我们尝试一次考虑一个子数字,并为每个子问题委派发现剩余的分解到递归调用(n-1)个篮子之间的重新分配。 处理特定子编号(在 for 循环中)时会考虑请求的阈值。

      import java.util.ArrayList;
      import java.util.List;
      
      public class TestFigures {
      
          public static List<List<Integer>> computeRepartitionNumber(int number_to_decompose, int number_of_subnumbers, int threshold_number) {
              List<List<Integer>> resultRec = new ArrayList<>();
      
              if (number_of_subnumbers == 1) {
                  List<List<Integer>> resultEnd = new ArrayList<>();
                  ArrayList<Integer> unitary = new ArrayList<>();
                  resultEnd.add(unitary);
                  unitary.add(number_to_decompose);
                  return resultEnd;
              }
      
              for (int i = threshold_number; i <= number_to_decompose-threshold_number; i++) {
                  int remain = number_to_decompose - i;
                  List<List<Integer>> partialRec = computeRepartitionNumber(remain, number_of_subnumbers - 1, threshold_number);
                  for(List<Integer> subList : partialRec){
                      subList.add(i);             
                  }
                  resultRec.addAll(partialRec);
              }
              return resultRec;
      
          }
      
          public static void main(String[] args) {
              List<List<Integer>> superlist = computeRepartitionNumber(5, 2, 1);
              System.out.println(superlist.size());
              System.out.println(superlist);
      
          }
      
      }
      

      【讨论】:

      • 如果您可以编辑您的答案并添加对您提供的代码的简短说明,以提高答案的质量。
      【解决方案6】:
      import random
      
      def split_given_number_into_n_random_numbers(number, number_of_subsections, min_random_number_desired = 0):
          cumulative_sum_of_random_numbers = 0
          current_subsection = 1
          max_random_number = int(number/number_of_subsections)
          if min_random_number_desired > max_random_number:
              print("ERROR: Cannot have min number as {} and split {} in {} subsections".format(min_random_number_desired,
                                                                                                number, number_of_subsections))
              return False
      
          while (True):
              random_number = random.randint(min_random_number_desired, max_random_number)
              print("Random number {} = {}".format(current_subsection, random_number))
              cumulative_sum_of_random_numbers += random_number
              # print("Cumulative sum {}".format(sum_of_num))
              number -= random_number
              current_subsection += 1
              if current_subsection == number_of_subsections:
                  random_number = number
                  print("Random number {} = {}".format(current_subsection, random_number))
                  cumulative_sum_of_random_numbers += random_number
                  break
      
          print("Final cumulative sum of random numbers = {}".format(cumulative_sum_of_random_numbers))
          return True
      
      if __name__ == '__main__':
          split_given_number_into_n_random_numbers(50, 7, 2)
      

      现在,如果您希望最小数量为 2 以外的其他值,请将其更改为提供的任何值 number_of_subsections * min_random_number_desired

      【讨论】:

      • 样本输出:随机数 1 = 6 随机数 2 = 5 随机数 3 = 2 随机数 4 = 5 随机数 5 = 2 随机数 6 = 4 随机数 7 = 26 最终随机数的累积总和 = 50
      【解决方案7】:

      我知道已经有很长时间了,但我想添加我的答案来帮助某人这里是我使用递归的代码

      #include <stdio.h>
      #include <stdlib.h>
      
      void print(int n, int * a) {
      int i ; 
      for (i = 0; i <= n; i++) {
          printf("%d", a[i]); 
         i < n ? printf(" + ") : printf("");
      }
      printf("\n"); 
      }
      
      void integerPartition(int n, int * a, int level){
      int first; 
      int i; 
      if (n < 1) return ;    
          a[level] = n;
      print(level, a);
      first = (level == 0) ? 1 : a[level-1];
      for(i = first; i <= n / 2; i++){
          a[level] = i; 
          integerPartition(n - i, a, level + 1);
      }
      }
      int main(int argc, char ** argv){
      int n = 10;     
      int * a = (int * ) malloc(sizeof(int) * n); 
      integerPartition (n, a, 0); 
      return(0);
      }
      

      这里 n 等于 10 但你可以让它像询问用户一样,使用 new 运算符声明 a 的大小!

      【讨论】:

        【解决方案8】:

        让我用 Python 编写它。

        假设您有 50 个元素要分成 7 个框,并且您希望每个框至少有两个。

        N_init = 50
        s = 2
        m = 7
        

        默认情况下,我们在每个框中放置 s 个元素,因此我们只剩下 N 个元素。

        N = N_init - s*m
        

        我们抽取 m 个随机数,对它们进行排序,在后面附加 N。这就像在一本 N 页的书中随机插入 m 个书签。 连续书签之间的页数是随机的。 (我们有 s 是为了确保每个盒子至少有 s 个元素)

        a = sorted([random.randint(0,N+1) for i in range(m)])
        a.append(N)
        result = [j-i+s for(i,j) in zip(a[0:m],a[1:m+1])]
        

        完成!

        【讨论】:

          【解决方案9】:

          我正在研究类似的东西,这就是我想出的。

          您可以在 O(N-1) 中在每个步骤中使用一些计算来做到这一点。您首先在每个点的最小数字和最大数字之间选择一个随机数字。对于每个点,通过从剩余余额中减去 (Min_Number * Remaining_Spots) 来计算最大数量。

          例如:对于第一个点,您选择一个介于 2 和 38 之间的数字。您可以通过从 50 中减去 (7-1)*2 来得到它。即 50 - 12 = 38。

          一旦你选择了一个数字,比如说 19,那么下一个数字的范围是 2-21。即 50-19-(5*2) = 21..

          ..等等。

          这里是sn-p的代码:

          function splitNumIntoXRandomComponents(num, x, min_num) {
          
          var components = [];    
          
          var count = 1;
          var cumulative = 0;
          var balance = num;
          
          for (var i = 0; i<x-1; i++) {
          
              //max num for this spot
              var max_num = balance - ((x-count)*min_num);
          
              //to avoid big numbers in the beginning and min numbers at the end
              if (Math.random() > 0.5){ //0.5 can be tuned to your liking 
                  max_num = Math.floor(max_num / 2) + min_num;
              }
          
              //generate the number for the spot at 'count'
              var c = Math.floor(Math.random()*(max_num-min_num+1)+min_num);
          
              //adjust balances
              cumulative += c;
              balance -= c;       
              count++;    
          
              //store this number
              components.push(c);                     
          
          }
          
          //push remaining balance into the last spot
          components.push(balance);
          
          //print numbers
          console.log(components);
          
          }
          
          for (var i=0; i<10; i++) {
              splitNumIntoXRandomComponents(50, 7, 2);
          }
          

          这是示例输出:

          [34, 2, 4, 3, 3, 2, 2]
          [14, 12, 8, 8, 4, 2, 2]
          [7, 4, 26, 5, 2, 3, 3]
          [8, 2, 16, 4, 4, 9, 7]
          [20, 8, 4, 4, 7, 4, 3]
          [3, 34, 4, 2, 2, 2, 3]
          [10, 5, 15, 2, 7, 5, 6]
          [6, 3, 10, 4, 10, 3, 14]
          [31, 4, 2, 3, 5, 2, 3]
          [7, 5, 2, 9, 9, 2, 16]
          

          这里是 jsFiddle: http://jsfiddle.net/wj81kvsc/6/

          【讨论】:

            猜你喜欢
            • 2022-01-15
            • 2013-03-21
            • 2017-04-22
            • 1970-01-01
            • 2023-03-28
            • 1970-01-01
            • 1970-01-01
            • 2011-10-04
            • 1970-01-01
            相关资源
            最近更新 更多