簡體   English   中英

找到所有固定大小的獨特組合以達到給定的平均范圍

[英]Find all unique combinations of a fixed size to reach a given average range

我有一個整數范圍,例如

big_list = [1, 2, ..., 100]

我需要找到這個范圍內所有固定長度的數字子集,當 k=5 時,k 的平均值在 50 以內(比如 45-55)。 例如,我們的固定大小為 6,平均約為 50

sample = [71, 20, 23, 99, 25, 60]

問題是列表必須是唯一的,沒有重復的數字。

順序無關緊要,因此 [71, 20, 23, 99, 25, 60] 和 [20, 71, 23, 99, 25, 60] 只是一種組合。

我想只使用 itertools 來生成所有組合並根據我的標准過濾掉。 但是它的運行時間會非常糟糕,因為大數字列表的范圍可能從 10 到 400。

如何使用上述標准生成一組列表

訂單是微不足道的。

只需安排i-th數字大於(i-1)-th 在下面的算法中,您通過left遞增 1 來遞歸

要獲得 45 到 55 之間的平均值,請考慮遞歸公式

import math

# left: previous number is left-1
# s: current sum
# d: depth
# c: combination
def fill(left, s, d, c):
  if d == 0:
    return print(c, sum(c)/n)

  # constraint c_sum >= 45*n
  # we look for minimal i such that 
  # sum + i + 100+99+...+(100-(depth-1)+1) >= 45*n
  # sum + i + 100*(d-1) - (d-2)(d-1)/2 >= 45*n
  # i >= 45*n - 100*(d-1) - (d-2)(d-1)/2 - sum
  # 
  # constraint c_sum <= 55*n
  # we look for maximal right such that
  # sum + i + (i+1)+...+(i+(d-1)) <= 55*n
  # sum + (d-1)*i + d(d-1)/2 <= 55*n
  # i <= ( 55*n - d(d-1)/2 - sum )/(d-1)
  minleft = max(left, math.ceil(minBound*n - 100*(d-1) - (d-2)*(d-1)/2 - s))
  if d == 1:
    maxright = min(100, maxBound*n-s)
  else:
    maxright = min(100, math.floor(( maxBound*n - d*(d-1)/2 - s )/(d-1)) )
  for i in range(minleft, maxright+1):
    newsum = s + i
    c[d-1] = i
    fill(i+1, newsum, d-1, c)

n = 6
minBound = 45
maxBound = 55
fill(0, 0, n, [0]*n)

在進一步評論之后,op 對上述組合完全不感興趣,但在組合中沒有任何數字可以在所有組合中出現兩次

算法可以簡化為非常基本的算法:

n = 300
c = list(range(1, n))
while len(c) >= 6:
  print([c.pop(), c.pop(), c.pop(), c.pop(0), c.pop(0), c.pop(0)])

問題的決定版本如下:

是否存在n個元素的子集,其平均值約為k

這是NP-Complete 子集總和問題的“放松”:

是否存在n個元素的子集,其總和恰好為kn

與其想要一個總和精確為kn的子集,您不願意接受一個總和為s | | -kn |的子集 <r 但是,如果r小,則可以將問題簡化為2r-1個子集和的實例。

我對子總和問題了解不多,但是根據Wikipedia頁面,存在用於決策問題的多項式時間近似算法,以及使用動態規划的偽多項式時間精確算法。

您想要列舉所有解決方案而不是決策的事實使事情變得更加復雜。 最壞情況下的復雜性可能真的很糟糕。 您真的需要所有解決方案嗎?

我將搜索“子集和近似枚舉”之類的東西。

您可以將遞歸與生成器一起使用:

def combo(d, k, c = []):
   if len(c) == 6:
      yield c
   else:
      for i in d:
        _c = (sum(c)+i)/float(len(c)+1)
        if i not in c and (len(c) + 1 < 6 or 50-k <= _c <= 50+k):
            yield from combo(d, k, c+[i])

當然,正如@japreiss 指出的那樣,這個問題會產生非常糟糕的最壞情況時間復雜度。 但是,一種可能的解決方法是將combo視為迭代器池,並根據需要在代碼中的其他地方訪問生成的組合。 例如,要訪問前 100 個結果:

result = combo(range(1, 100), 5)
for _ in range(100):
   print(next(result))

輸出:

[1, 2, 3, 67, 98, 99]
[1, 2, 3, 67, 99, 98]
[1, 2, 3, 68, 97, 99]
[1, 2, 3, 68, 98, 99]
[1, 2, 3, 68, 99, 97]
[1, 2, 3, 68, 99, 98]
[1, 2, 3, 69, 96, 99]
[1, 2, 3, 69, 97, 98]
[1, 2, 3, 69, 97, 99]
[1, 2, 3, 69, 98, 97]
...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM