在给定的范围内,找到具有指定数字的整数的算法

4
如果我有一组数字的完整列表,即list,并且我想知道在给定范围[A, B]内可以形成多少个(有效)整数,那么我可以使用什么算法来高效地完成它?
例如,给定一个数字列表(包含重复和零)list={5, 3, 3, 2, 0, 0},我想知道在包括[20, 400]在内的范围内可以形成多少个整数。例如,在这种情况下,20, 23, 25, 30, 32, 33, 35, 50, 52, 53, 200, 203, 205, 230, 233, 235, 250, 253, 300, 302, 303, 305, 320, 323, 325, 330, 332, 335, 350, 352, 353都是有效的。

@NiklasB.:不,这不是作业。 - user1096734
好的,只是想确认一下 :) - Niklas B.
3个回答

2
Step 1: Find the number of digits your answers are likely to fall in. In your 
        example it is 2 or 3.

Step 2: For a given number size (number of digits)

    Step 2a: Pick the possibilities for the first (most significant digit). 
    Find the min and max number starting with that digit (ascend or descending
    order of rest of the digits). If both of them fall into the range:
        step 2ai: Count the number of digits starting with that first digit and
        update that count
    Step 2b: Else if both max and min are out of range, ignore. 
    Step 2c: Otherwise, add each possible digit as second most significant digit
    and repeat the same step 

通过案例解决问题:

对于数字大小为2,即__:

0_ : Ignore since it starts with 0
2_ : Minimum=20, Max=25. Both are in range. So update count by 3 (second digit might be 0,3,5)
3_ : Minimum=30, Max=35. Both are in range. So update count by 4 (second digit might be 0,2,3,5)
5_ : Minimum=50, Max=53. Both are in range. So update count by 3 (second digit might be 0,2,3)

对于大小为3:

0__ : Ignore since it starts with 0
2__ : Minimum=200, max=253. Both are in range. Find the number of ways you can choose 2 numbers from a set of {0,0,3,3,5}, and update the count.
3__ : Minimum=300, max=353. Both are in range. Find the number of ways you can choose 2 numbers from a set of {0,0,2,3,5}, and update the count.
5__ : Minimum=500, max=532. Both are out of range. Ignore.

一个更有趣的情况是当最大限制为522时(而不是400):
5__ : Minimum=500, max=532. Max out of range.
    50_: Minimum=500, Max=503. Both in range. Add number of ways you can choose one digit from {0,2,3,5}
    52_: Minimum=520, Max=523. Max out of range.
        520: In range. Add 1 to count.
        522: In range. Add 1 to count.
        523: Out of range. Ignore.
    53_: Minimum=530, Max=532. Both are out of range. Ignore.



def countComb(currentVal, digSize, maxVal, minVal, remSet):
    minPosVal, maxPosVal = calculateMinMax( currentVal, digSize, remSet)
    if maxVal>= minPosVal >= minVal and maxVal>= maxPosVal >= minVal
        return numberPermutations(remSet,digSize, currentVal)
    elif minPosVal< minVal and maxPosVal < minVal or minPosVal> maxVal and maxPosVal > maxVal:
        return 0
    else:
        count=0
        for k in unique(remSet):
            tmpRemSet = [i for i in remSet]
            tmpRemSet.remove(k)
            count+= countComb(currentVal+k, digSize, maxVal, minVal, tmpRemSet)
        return count

在您的情况下:countComb('',2,400,20,['0','0','2','3','3','5']) + countComb('',3,400,20,['0','0','2','3','3','5']) 将会给出答案。
def calculateMinMax( currentVal, digSize, remSet):
    numRemain = digSize - len(currentVal)
    minPosVal = int( sorted(remSet)[:numRemain] )
    maxPosVal = int( sorted(remSet,reverse=True)[:numRemain] )
    return minPosVal,maxPosVal

numberPermutations(remSet,digSize, currentVal): Basically number of ways 
you can choose (digSize-len(currentVal)) values from remSet. See permutations
with repeats.

我只是在数字列表中添加了一个“5”,以便理解您的第一步。 - user1096734
看起来相当复杂。运行时间是多少? - user1096734
@littleEinstein,这并不是很复杂,因为你可以使用递归来实现。复杂度大约为n*k,其中n是可能候选人的不同数量,k是您需要考虑的整数大小的数量。如果您的范围是10^6到10^20,那么这非常高效。如果您的范围看起来像[123456, 213456],那么它略微低效(但比线性搜索好)。 - ElKamina
请问您能否按照你说过的要求,编写一个使用递归的可运行代码吗?谢谢! - user1096734
@littleEinstein 我会写这个代码,但是我要假设1、01和001是不同的可能性。否则代码会变得过于复杂。 - ElKamina
@littleEinstein 现在代码看起来更完整了。请看一下。 - ElKamina

0
对于一个有n个数字的列表,其中z个是零,有一个下限l和一个上限u...
第1步:容易的部分
考虑这样一种情况:你有一个两位数的下限和一个四位数的上限。尽管确定在范围内有多少个两位数和四位数可能有点棘手,但我们至少知道所有的三位数都在范围内。如果范围是一个两位数和一个五位数,那么所有的三位数和四位数都可以使用。
因此,让我们将其推广到一个有a个数字的下限和一个有b个数字的上限。对于a和b之间的每个k(不包括a和b本身),所有的k位数字都在范围内。
有多少这样的数字?考虑如何选择它们:第一个数字必须是非零的 n 个数字之一(因此是 (n - z) 个数字),其余数字从尚未选择的列表中选择,即第二个数字有 (n-1) 种选择,第三个数字有 (n-2) 种选择,以此类推。因此,这看起来像是一个阶乘,但第一项很奇怪。有多少个 n 中的数字被选中了?为什么是其中的 k 个,这意味着我们必须除以 (n - k)!,以确保我们总共只选择了 k 个数字。因此,每个 k 的方程看起来像这样:(n - z)(n - 1)!/(n - k)! 将范围内的每个 k (从 ab)代入方程,就可以得到可能的 (a+1) 到 (b-1) 位数字的数量,所有这些数字都必须有效。

步骤2:边界情况

当考虑到 ab 位数时,情况会变得有些棘手。我认为您无法避免开始深度优先搜索所有可能的数字组合,但是如果它超出了边界,您可以至少中止整个分支。

例如,如果您的列表包含 {7、5、2、3、0},并且您的上限为520,您的搜索可能会像以下示例一样进行:

Pick the 7: does 7 work in the hundreds place? No, because 700 > 520;
  abort this branch entirely (i.e. don't consider 752, 753, 750, 725, etc.)
Pick the 5: does 5 work in the hundreds place? Yes, because 500 <= 520.
    Pick the 7: does 7 work in the tens place? No, because 570 > 520.
      Abort this branch (i.e. don't consider 573, 570, etc.)
    Pick the 2: does 2 work in the tens place? Yes, because 520 <= 520.
        Pick the 7: does 7 work in the ones place? No, because 527 > 520.
        Pick the 3: does 3 work in the ones place? No, because 523 > 520.
        Pick the 0: does 0 work in the ones place? Yes, because 520 <= 520.
        Oh hey, we found a number. Make sure to count it. 
    Pick the 3: does 3 work in the tens place? No; abort this branch.
    Pick the 0: does 0 work in the tens place? Yes.
        ...and so on. 

...然后你会为下限做同样的事情,但是翻转比较器。这不像在(a, b)区间内的k位数字组合那么高效(即O(1)),但至少你可以通过尽早剪枝必须不可能的分支来避免很多工作。无论如何,这种策略确保您只需要实际枚举两个边界情况,而不管您的(a, b)区间有多宽(或者如果您的下限为0,则只有一个边界情况)。

编辑:

我忘了提到一些东西(抱歉,我在回家的公交车上打的所有字):

当进行深度优先搜索时,实际上只有在第一个数字等于边界的第一个数字时才需要递归。也就是说,如果您的边界是520,而您刚刚选择了3作为第一个数字,您可以立即添加(n-1)!/(n-3)!并跳过整个分支,因为以300开头的所有3位数肯定都低于500。


0
如果范围很小但列表很大,简单的解决方案就是循环遍历范围并检查每个数字是否可以从列表中生成。使用哈希表或数组进行检查可以快速完成,其中数组记录列表中每个数字仍可使用的次数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接