排列没有重复

时间:2015-11-08 14:54:58

标签: algorithm permutation

我想知道,解决这个问题的最佳方法是什么:

给定x,y和y整数:a1,a2,a3 .. ay找到所有组合 a1±a2±...±ay = x,y < 20。

我最近的方法是找到存储在表T中的1和0的所有排列,然后,根据数字T [i]是1还是0,从sum加上或减去ai。问题是有n! n元素阵列的排列。因此,对于20元素阵列,我必须检查20!大多数重复的可能性。你能否建议我解决问题的任何可能方法?

2 个答案:

答案 0 :(得分:2)

只有2 ^ 20(超过一百万)长度为20的二进制向量而不是不可行的20!使用应该能够在不到一秒的时间内强制使用少量,特别是如果你使用Gray Code,这将允许你在一个步骤中从一个候选总和传递到另一个候选总和(例如从{{1只需添加a + b - c -d。{/ p>即可{}} a + b - c + d

如果2*d变得更大,那么@MikeWise的优秀分支和约束理念会很好。生成以y的根节点开头的树。给孩子0-a1。然后通过添加和减去+a1等4个大孩子。如果你比目标a2中的剩余ai的总和还要远 - 你可以修剪那个分支。在最坏的情况下,这可能比基于格雷码的蛮力稍差(因为你需要在每个节点上做更多的处理),但在最好的情况下,你可能能够删除大多数可能性。

On Edit:这是一些Python代码。首先,我定义一个生成器,给定一个整数x,连续返回需要翻转的位位置以逐步执行格雷码:

n

(这使用了我多年前在Stanton和White的优秀着作“Constructive Combinatorics”中学到的算法)。

然后 - 我用它来返回所有解决方案(作为包含根据需要插入负号的数字输入列表的列表)。关键是我可以采用当前的位翻转,并加上或减去相应数字的两倍:

def grayBit(n):
    code = [0]*n
    odd = True
    done = False
    while not done:
        if odd:
            code[0] = 1 - code[0] #flip bit
            odd = False
            yield 0
        else:
            i = code.index(1)
            if i == n-1:
                done = True
            else:
                code[i+1] = 1 - code[i+1]
                odd = True
                yield i+1

典型输出:

def signedSums(nums, target):
    n = len(nums)
    patterns = []
    total = sum(nums)
    pattern = [1]*n
    if target == total: patterns.append([x*y for x,y in zip(nums,pattern)])
    deltas = [2*i for i in nums]
    for i in grayBit(n):
        if pattern[i] == 1:
            total -= deltas[i]
        else:
            total += deltas[i]
        pattern[i] = -1 * pattern[i]
        if target == total: patterns.append([x*y for x,y in zip(nums,pattern)])
    return patterns

评估只需要大约一秒钟:

>>> signedSums([1,2,3,4,5,9],6)
[[1, -2, -3, -4, 5, 9], [1, 2, 3, -4, -5, 9], [-1, 2, -3, 4, -5, 9], [1, 2, 3, 4, 5, -9]]

因此,有2865种方法可以在1,2,...,20范围内加上或减去整数,以获得100的净值。

我认为可以添加或减去>>> len(signedSums([i for i in range(1,21)],100)) 2865 (而不是仅添加,这是你的问题所暗示的字面意思)。请注意,如果您确实要坚持a1肯定会发生,那么您可以从a1中减去它,并将上述算法应用于列表的其余部分和调整后的目标。

最后,如果您使用权重集x解决subset sub problem并且目标总和为{2*a1, 2*a2, 2*a3, .... 2*ay},那么选择的子集将完全对应并不难在原始问题的解决方案中出现积极迹象的子集。因此,您的问题很容易简化为子集和问题,因此确定它是否具有任何解决方案(并且NP难以列出所有解决方案)是NP完全的。

答案 1 :(得分:1)

我们有条件:

a1 ± a2 ± ... ± ay = x, y<20   [1]

首先,我会概括条件[1],允许所有'a'包括'a1'为±:

±a1 ± a2 ± ... ± ay = x   [2]

如果我们有[2]的解决方案,我们可以很容易地得到[1]

的解决方案

要解决[2],我们可以使用以下方法:

combinations list x 
    | x == 0 && null list = [[]]
    | null list = []
    | otherwise = plusCombinations ++ minusCombinations where
    a = head list
    rest = tail list
    plusCombinations = map (\c -> a:c) $ combinations rest (x-a)
    minusCombinations = map (\c -> -a:c) $ combinations rest (x+a)

说明:

  1. 第一个条件检查x是否达到零并使用列表中的所有数字。这意味着找到了解决方案,我们返回单一解决方案:[[]]

  2. 第二个条件检查列表是否为空,只要x不为0,这意味着找不到解,返回空解:[]

  3. 第三个分支意味着我们可以有两种选择:使用带有'+'或' - '的ai,所以我们连接加号和减号组合

  4. 示例输出:

    *Main> combinations [1,2,3,4] 2
    [[1,2,3,-4],[-1,2,-3,4]]
    *Main> combinations [1,2,3,4] 3
    []
    *Main> combinations [1,2,3,4] 4
    [[1,2,-3,4],[-1,-2,3,4]]