Python – 统计所有从1到N中的K个数的组合,它们的和等于N。

huangapple go评论104阅读模式
英文:

Python - Count all combinations of K numbers from 1-N whose sum is equal to N

问题

如何计算所有从1到n中选取k个数字的组合,使它们的总和等于n?例如,对于n = 10,k = 3,我们有(1, 2, 7),(1, 3, 6),(1, 4, 5),(2, 3, 5)。

我尝试过使用itertools.combination,但对于大数字而言,它增长得非常快。

英文:

How do i count all combinations of k numbers from 1-n whose sum is equal to n? Like for n = 10, k = 3, we have (1, 2, 7), (1, 3, 6), (1, 4, 5), (2, 3, 5)

I've tried using itertools.combination but it grows really fast for big numbers

答案1

得分: 2

我们可以用 k 个不同的正整数制造出的最小数是 choose(k+1, 2)。

令 r(n,k) = n - choose(k+1, 2)。

那么从 k 个不同的整数中形成 n 的方式数量等于将 k 个非负、不一定不同的整数相加以得到 r(n,k) 的方式数量。思路是从 1, 2, 3, ..., k 开始,然后以非递减的方式分配 r(n,k) 到这些起始整数上。

例如,对于 10, 3:

1 + 2 + 3 = choose(4,2) = 6,所以 r(10,3) = 10-6 = 4。
4 = 0+0+4, 0+1+3, 0+2+2, 1+1+2
(1,2,3) + (0,0,4) = (1,2,7)
(1,2,3) + (0,1,3) = (1,3,6)
(1,2,3) + (0,2,2) = (1,4,5)
(1,2,3) + (1,1,2) = (2,3,5)

所以我们将问题简化为计算 k 个非负整数相加得到 r(n,k) 的方式数量。在[这里][1]有答案。

Ruby 代码(包括实用函数):

  1. def choose(n, k)
  2. return 0 if k > n
  3. result = 1
  4. 1.upto(k) do |d|
  5. result *= n
  6. result /= d
  7. n -= 1
  8. end
  9. return result
  10. end
  11. def count_partitions_nozeroes(n, k, cache = {})
  12. return 1 if k==0 && n==0
  13. return 0 if n <= 0 || k <= 0
  14. # 检查结果是否已被记忆
  15. if cache.key?([n, k])
  16. return cache[[n, k]]
  17. end
  18. # 计算结果
  19. result = count_partitions_nozeroes(n-1, k-1, cache) + count_partitions_nozeroes(n-k, k, cache)
  20. # 记忆结果以备将来使用
  21. cache[[n, k]] = result
  22. return result
  23. end
  24. def count_partitions_zeros(n,k)
  25. return count_partitions_nozeroes(n+k, k)
  26. end
  27. def solve(n,k)
  28. r = n - choose(k+1,2)
  29. return count_partitions_zeros(r,k)
  30. end

示例结果:

  1. > solve(10,3)
  2. => 4
  3. > solve(200,10)
  4. => 98762607
  5. > solve(2000,10)
  6. => 343161146717017732
  7. > solve(2000,100) # 没有解决方案是正确的
  8. => 0
  9. > solve(2000,40)
  10. => 2470516759655914864269838818691
  11. > solve(5000,50)
  12. => 961911722856534054414857561149346788190620561928079
  13. > solve(9000,100)
  14. => 74438274524772625088229884845232647085568457172246625852148213

这是一个更简单的 Ruby 版本,避免了递归(其他方法不变)。它给出与上述相同的结果。下面展示了一些更大数字的结果。这个版本的复杂度是 O(n*r)。

  1. def count_partitions_nozeroes(n, k)
  2. n_to_k_to_count = Hash.new{|h, n| h[n] = Hash.new{|h2, k| h2[k] = 0}}
  3. n_to_k_to_count[n][k] = 1
  4. (n).downto(1) do |cur_n|
  5. n_to_k_to_count.delete(cur_n + 1) # 删除旧键以节省空间
  6. n_to_k_to_count[cur_n].keys.each do |cur_k|
  7. n_to_k_to_count[cur_n - 1][cur_k - 1] += n_to_k_to_count[cur_n][cur_k] if cur_n >= 1 && cur_k >= 1
  8. n_to_k_to_count[cur_n - cur_k][cur_k] += n_to_k_to_count[cur_n][cur_k] if cur_n >= cur_k && cur_k >= 0
  9. end
  10. end
  11. return n_to_k_to_count[0][0]
  12. end

示例结果:

  1. > solve(10_000, 100)
  2. => 274235043379646744332574760930015102932669961381003514201948469288939
  3. > solve(20_000, 100)
  4. => 7299696028160228272878582999080106323327610318395689691894033570930310212378988634117070675146218304092757
  5. > solve(30_000, 100)
  6. => 272832080760303721646457320315409638838332197621252917061852201523368622283328266190355855228845140740972789576932357443034296
  7. > solve(40_000, 200)
  8. => 1207940070190155086319681977786735094825631330761751426889808559216057614938892266960158470822904722575922933920904751545295375665942760497367
  9. > solve(100_000, 200)
  10. => 13051215883535384859396062192804954511590479767894013629996324213956689010966899432038449004533035681835942448619230013858515264041486939129111486281204426757510182253404556858519289275662797170197384965998425620735381780708992863774464769
  11. > solve(1_000_000, 200) # 变得非常慢;3.5 分钟
  12. => 428880856178598710720148624933560494061607079247573557573778067722670591454531582929217788942407876811003263888596981076595546473767426764847052870957098719920895206333233661830556744660481006393060648337767876434226805997102371290790505388472758064159747958795845134023811256732973394383039538732268993828238034324648751357082834429815006950891214256221354725682849015159958577756592134668188434645414960901194459625871943042
  13. <details>
  14. <summary>英文:</summary>
  15. The smallest number we can make with k distinct positive integers is choose(k+1, 2).
  16. Let r(n,k) = n - choose(k+1, 2).
  17. Then the count of ways of forming n from k distinct integers is equal to the count of ways of summing k nonnegative non-necessarily-distinct integers to get r(n,k). The idea is we start with 1, 2, 3, ..., k, and then allocate r(n,k) to these starting integers in a nondecreasing manner.
  18. E.g., 10, 3:
  19. 1 + 2 + 3 = choose(4,2) = 6, so r(10,3) = 10-6 = 4.
  20. 4 = 0+0+4, 0+1+3, 0+2+2, 1+1+2
  21. (1,2,3) + (0,0,4) = (1,2,7)
  22. (1,2,3) + (0,1,3) = (1,3,6)
  23. (1,2,3) + (0,2,2) = (1,4,5)
  24. (1,2,3) + (1,1,2) = (2,3,5)
  25. So we&#39;ve reduce the problem to counting the number of ways of summing k nonnegative integers to get r(n,k). Answered [here][1]
  26. Ruby code (including util functions):
  27. def choose(n, k)
  28. return 0 if k &gt; n
  29. result = 1
  30. 1.upto(k) do |d|
  31. result *= n
  32. result /= d
  33. n -= 1
  34. end
  35. return result
  36. end
  37. def count_partitions_nozeroes(n, k, cache = {})
  38. return 1 if k==0 &amp;&amp; n==0
  39. return 0 if n &lt;= 0 || k &lt;= 0
  40. # Check if the result is already memoized
  41. if cache.key?([n, k])
  42. return cache[[n, k]]
  43. end
  44. # Calculate the result
  45. result = count_partitions_nozeroes(n-1, k-1, cache) + count_partitions_nozeroes(n-k, k, cache)
  46. # Memoize the result for future use
  47. cache[[n, k]] = result
  48. return result
  49. end
  50. def count_partitions_zeros(n,k)
  51. return count_partitions_nozeroes(n+k, k)
  52. end
  53. def solve(n,k)
  54. r = n - choose(k+1,2)
  55. return count_partitions_zeros(r,k)
  56. end
  57. Sample results
  58. &gt; solve(10,3)
  59. =&gt; 4
  60. &gt; solve(200,10)
  61. =&gt; 98762607
  62. &gt; solve(2000,10)
  63. =&gt; 343161146717017732
  64. &gt; solve(2000,100) # correct that there&#39;s no solution
  65. =&gt; 0
  66. &gt; solve(2000,40)
  67. =&gt; 2470516759655914864269838818691
  68. &gt; solve(5000,50)
  69. =&gt; 961911722856534054414857561149346788190620561928079
  70. &gt; solve(9000,100)
  71. =&gt; 74438274524772625088229884845232647085568457172246625852148213
  72. Here&#39;s a simpler Ruby version that avoids recursion (other methods unchanged). It gives the same results as above. A few results for larger numbers shown below. This version is O(n*r).
  73. def count_partitions_nozeroes(n, k)
  74. n_to_k_to_count = Hash.new{|h, n| h[n] = Hash.new{|h2, k| h2[k] = 0}}
  75. n_to_k_to_count[n][k] = 1
  76. (n).downto(1) do |cur_n|
  77. n_to_k_to_count.delete(cur_n + 1) # delete old keys to save space
  78. n_to_k_to_count[cur_n].keys.each do |cur_k|
  79. n_to_k_to_count[cur_n - 1][cur_k - 1] += n_to_k_to_count[cur_n][cur_k] if cur_n &gt;= 1 &amp;&amp; cur_k &gt;= 1
  80. n_to_k_to_count[cur_n - cur_k][cur_k] += n_to_k_to_count[cur_n][cur_k] if cur_n &gt;= cur_k &amp;&amp; cur_k &gt;= 0
  81. end
  82. end
  83. return n_to_k_to_count[0][0]
  84. end
  85. Sample results
  86. &gt; solve(10_000, 100)
  87. =&gt; 274235043379646744332574760930015102932669961381003514201948469288939
  88. &gt; solve(20_000, 100)
  89. =&gt; 7299696028160228272878582999080106323327610318395689691894033570930310212378988634117070675146218304092757
  90. &gt; solve(30_000, 100)
  91. =&gt; 272832080760303721646457320315409638838332197621252917061852201523368622283328266190355855228845140740972789576932357443034296
  92. &gt; solve(40_000, 200)
  93. =&gt; 1207940070190155086319681977786735094825631330761751426889808559216057614938892266960158470822904722575922933920904751545295375665942760497367
  94. &gt; solve(100_000, 200)
  95. =&gt; 13051215883535384859396062192804954511590479767894013629996324213956689010966899432038449004533035681835942448619230013858515264041486939129111486281204426757510182253404556858519289275662797170197384965998425620735381780708992863774464769
  96. &gt; solve(1_000_000, 200) # getting painfully slow; 3.5 mins
  97. =&gt; 42888085617859871072014862493356049406160707924757355757377806772267059145453158292921778894240787681100326388859698107659554647376742676484705287095709871992089520633323366183055674466048100639306064833776787643422680599710237129079050538847275806415974795879584513402381125673297339438303953873226899382823803432464875135708283442981500695089121425622135472568284901515995857775659213466818843464541496090119445962587194304280691087464026800781
  98. [1]: https://math.stackexchange.com/questions/217597/number-of-ways-to-write-n-as-a-sum-of-k-nonnegative-integers
  99. </details>
  100. # 答案2
  101. **得分**: 1
  102. 使用带有缓存的递归方法可以在合理的时间内产生结果:
  103. ```python
  104. from functools import lru_cache
  105. @lru_cache(None)
  106. def countNK(n, k, t=None):
  107. t = n if t is None else t # 跟踪目标部分和
  108. if k == 0: return int(t==0) # 空集可以求和为零
  109. if t < 1: return 0 # 仅有效目标
  110. if k > n: return 0 # 值不够
  111. return countNK(n-1, k, t) + countNK(n-1, k-1, t-n) # 合并计数
  • 递归需要以逐渐减小的n值来寻找目标
  • 它还需要在从目标中删除每个值后对较短的组合进行相同的操作
  • 这将多次合并相同的计算,因此需要缓存

输出:

  1. print(countNK(10,3)) # 4
  2. print(countNK(200,10)) # 98762607

如果您需要处理较大的n值(例如500+),您需要增加递归限制或将函数转换为迭代循环。

英文:

A recursive approach with caching can produce results in a reasonable time:

  1. from functools import lru_cache
  2. @lru_cache(None)
  3. def countNK(n,k,t=None):
  4. t = n if t is None else t # track target partial sum
  5. if k == 0: return int(t==0) # empty set can sum to zero
  6. if t &lt; 1 : return 0 # valid target only
  7. if k &gt; n : return 0 # not enough values
  8. return countNK(n-1,k,t)+countNK(n-1,k-1,t-n) # combine counts
  • recursion needs to aim for a target using progressively smaller values of n
  • it also needs to do that for shorter combinations after removing each value from the target
  • this will combine the same calculations multiple times, hence the caching

...

output:

  1. print(countNK(10,3)) # 4
  2. print(countNK(200,10)) # 98762607

If you need to process large values of n (e.g. 500+), you'll either need to increase the recursion limit or convert the function to an iterative loop

答案3

得分: 1

以下是您提供的代码的中文翻译部分:

  1. def Kelly(n, k):
  2. if k == 0:
  3. return 1 if n == 0 else 0
  4. @cache
  5. def c(n, k):
  6. if n < k * (k+1) // 2:
  7. return 0
  8. if k == 1:
  9. return 1
  10. n -= k
  11. return c(n, k) + c(n, k-1)
  12. return c(n, k)
  13. # 预先计算用于 "n < ..." 基本情况的界限
  14. def Kelly2(n, k):
  15. if k == 0:
  16. return 1 if n == 0 else 0
  17. min_n_for_k = list(accumulate(range(k+1)))
  18. @cache
  19. def c(n, k):
  20. if n < min_n_for_k[k]:
  21. return 0
  22. if k == 1:
  23. return 1
  24. n -= k
  25. return c(n, k) + c(n, k-1)
  26. return c(n, k)
  27. def Alain(n, k):
  28. @lru_cache(None)
  29. def countNK(n,k,t=None):
  30. t = n if t is None else t # 跟踪目标部分和
  31. if k == 0: return int(t==0) # 空集可以总和为零
  32. if t < 1 : return 0 # 仅有效目标
  33. if k > n : return 0 # 不足够的值
  34. return countNK(n-1,k,t)+countNK(n-1,k-1,t-n) # 合并计数
  35. return countNK(n, k)
  36. def Dave_translated_by_Kelly(n, k):
  37. def choose(n, k):
  38. if k > n: return 0
  39. result = 1
  40. for d in range(1, k+1):
  41. result *= n
  42. result //= d
  43. n -= 1
  44. return result
  45. def count_partitions_nozeroes(n, k, cache = {}):
  46. if k==0 and n==0: return 1
  47. if n <= 0 or k <= 0: return 0
  48. # 检查结果是否已被记忆
  49. if (n, k) in cache:
  50. return cache[n, k]
  51. # 计算结果
  52. result = count_partitions_nozeroes(n-1, k-1, cache) + count_partitions_nozeroes(n-k, k, cache)
  53. # 为将来的使用记忆结果
  54. cache[n, k] = result
  55. return result
  56. def count_partitions_zeros(n,k):
  57. return count_partitions_nozeroes(n+k, k)
  58. def solve(n,k):
  59. r = n - choose(k+1,2)
  60. return count_partitions_zeros(r,k)
  61. return solve(n, k)
  62. big = False
  63. funcs = Alain, Kelly, Kelly2, Dave_translated_by_Kelly
  64. if big:
  65. funcs = funcs[1:]
  66. from functools import lru_cache, cache
  67. from itertools import accumulate
  68. from time import perf_counter as time
  69. from statistics import mean, stdev
  70. import sys
  71. import gc
  72. # 正确性
  73. for n in range(51):
  74. for k in range(51):
  75. expect = funcs[0](n, k)
  76. for f in funcs[1:]:
  77. result = f(n, k)
  78. assert result == expect
  79. # 速度
  80. sys.setrecursionlimit(20000)
  81. times = {f: [] for f in funcs}
  82. def stats(f):
  83. ts = [t * 1e3 for t in sorted(times[f])[:5]]
  84. return f'{mean(ts):5.1f} ± {stdev(ts):4.1f} ms '
  85. for _ in range(25):
  86. for f in funcs:
  87. gc.collect()
  88. t0 = time()
  89. if big:
  90. f(9000, 100)
  91. else:
  92. for k in range(101):
  93. f(100, k)
  94. times[f].append(time() - t0)
  95. for f in sorted(funcs, key=stats):
  96. print(stats(f), f.__name__)

请注意,由于代码段包含特定的编程注释和功能,因此某些部分的翻译可能会失去一些上下文,但我已尽力保持了代码的完整性和一致性。

英文:

Benchmark with n=100 and all k from 0 to 100, Kelly* are my solutions:

  1. 2.5 &#177; 0.1 ms Kelly
  2. 2.8 &#177; 0.2 ms Kelly2
  3. 3.5 &#177; 0.2 ms Dave_translated_by_Kelly
  4. 295.0 &#177; 23.7 ms Alain

Let c(n, k) be the number of combinations with sum n with k different numbers 1 or larger.

We get: c(n, k) = c(n-k, k) + c(n-k, k-1)

You want sum n with k different numbers 1 or larger. You either use the number 1 in the combination or you don't.

  • If you don't use 1, then you want sum n with k different numbers 2 or larger. Imagine you had such k numbers. Subtract 1 from each of them, then you have sum n-k with k different numbers 1 or larger. That's c(n-k, k).
  • If you do use 1, then you want remaining sum n-1 with k-1 different numbers 2 or larger. Imagine you had such k-1 numbers. Subtract 1 from each of them, then you have sum (n-1)-(k-1) = n-k with k-1 different numbers 1 or larger. That's c(n-k, k-1).

The faster solutions with Dave's case n=9000, k=100:

  1. 469.1 &#177; 9.2 ms Kelly2
  2. 478.8 &#177; 17.0 ms Kelly
  3. 673.4 &#177; 18.8 ms Dave_translated_by_Kelly

Code (Attempt This Online!):

  1. def Kelly(n, k):
  2. if k == 0:
  3. return 1 if n == 0 else 0
  4. @cache
  5. def c(n, k):
  6. if n &lt; k * (k+1) // 2:
  7. return 0
  8. if k == 1:
  9. return 1
  10. n -= k
  11. return c(n, k) + c(n, k-1)
  12. return c(n, k)
  13. # Precompute the bounds for the &quot;n &lt; ...&quot; base case
  14. def Kelly2(n, k):
  15. if k == 0:
  16. return 1 if n == 0 else 0
  17. min_n_for_k = list(accumulate(range(k+1)))
  18. @cache
  19. def c(n, k):
  20. if n &lt; min_n_for_k[k]:
  21. return 0
  22. if k == 1:
  23. return 1
  24. n -= k
  25. return c(n, k) + c(n, k-1)
  26. return c(n, k)
  27. def Alain(n, k):
  28. @lru_cache(None)
  29. def countNK(n,k,t=None):
  30. t = n if t is None else t # track target partial sum
  31. if k == 0: return int(t==0) # empty set can sum to zero
  32. if t &lt; 1 : return 0 # valid target only
  33. if k &gt; n : return 0 # not enough values
  34. return countNK(n-1,k,t)+countNK(n-1,k-1,t-n) # combine counts
  35. return countNK(n, k)
  36. def Dave_translated_by_Kelly(n, k):
  37. def choose(n, k):
  38. if k &gt; n: return 0
  39. result = 1
  40. for d in range(1, k+1):
  41. result *= n
  42. result //= d
  43. n -= 1
  44. return result
  45. def count_partitions_nozeroes(n, k, cache = {}):
  46. if k==0 and n==0: return 1
  47. if n &lt;= 0 or k &lt;= 0: return 0
  48. # Check if the result is already memoized
  49. if (n, k) in cache:
  50. return cache[n, k]
  51. # Calculate the result
  52. result = count_partitions_nozeroes(n-1, k-1, cache) + count_partitions_nozeroes(n-k, k, cache)
  53. # Memoize the result for future use
  54. cache[n, k] = result
  55. return result
  56. def count_partitions_zeros(n,k):
  57. return count_partitions_nozeroes(n+k, k)
  58. def solve(n,k):
  59. r = n - choose(k+1,2)
  60. return count_partitions_zeros(r,k)
  61. return solve(n, k)
  62. big = False
  63. funcs = Alain, Kelly, Kelly2, Dave_translated_by_Kelly
  64. if big:
  65. funcs = funcs[1:]
  66. from functools import lru_cache, cache
  67. from itertools import accumulate
  68. from time import perf_counter as time
  69. from statistics import mean, stdev
  70. import sys
  71. import gc
  72. # Correctness
  73. for n in range(51):
  74. for k in range(51):
  75. expect = funcs[0](n, k)
  76. for f in funcs[1:]:
  77. result = f(n, k)
  78. assert result == expect
  79. # Speed
  80. sys.setrecursionlimit(20000)
  81. times = {f: [] for f in funcs}
  82. def stats(f):
  83. ts = [t * 1e3 for t in sorted(times[f])[:5]]
  84. return f&#39;{mean(ts):5.1f} &#177; {stdev(ts):4.1f} ms &#39;
  85. for _ in range(25):
  86. for f in funcs:
  87. gc.collect()
  88. t0 = time()
  89. if big:
  90. f(9000, 100)
  91. else:
  92. for k in range(101):
  93. f(100, k)
  94. times[f].append(time() - t0)
  95. for f in sorted(funcs, key=stats):
  96. print(stats(f), f.__name__)

答案4

得分: 0

让我们介绍一个函数:f(n,k,s)=从1到n中选择k个数字,它们的和为s的组合数。

为了解决这个任务,我们需要计算f(n,k,n)

这个函数可以通过递归计算。所有的组合可以分为两组:包含最大数字和不包含最大数字的组合,这给了我们f(n,k,s)=f(n-1,k-1,s-n)+f(n-1,k,s)。递归可以在以下情况下停止:

  • n小于k -> 0(数字不够)
  • k=1,s大于n -> 0(每个数字都太小)
  • k=1,s小于1 -> 0(每个数字都太小)
  • k=1,1<=s<=n -> 1(只有一个合适的数字)
  • s小于0 -> 0

N^2*k种可能的参数组合,所以如果我们缓存已经计算过的值,我们的复杂度将在O(N^3)范围内。

英文:

Let's introduce a function: f(n,k,s)=number of combinations of k numbers from 1 to n, having s as their sum.

To solve the task we need to calculate f(n,k,n).

The function may be calculated recursively. All combinations can be split into two groups: with and without the max number. That gives us f(n,k,s)=f(n-1,k-1,s-n)+f(n-1,k,s). The recursion may stop in the following cases:

  • n<k -> 0 (we don't have enough numbers)
  • k=1, s>n -> 0 (every number is too small)
  • k=1, s<1 -> 0 (every number is too small)
  • k=1, 1<=s<=n -> 1 (there is only one suitable number)
  • s<0 -> 0

There are N^2*k possible combinations of arguments, so if we cache the already calculated values, we will be within O(N^3).

huangapple
  • 本文由 发表于 2023年5月24日 20:53:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76323767.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定