重复排列 - 非字谜算法

Posted

技术标签:

【中文标题】重复排列 - 非字谜算法【英文标题】:Permutations with repetitions - Non-anagram algorithm 【发布时间】:2020-05-11 16:21:54 【问题描述】:

目标:我正在尝试查找包含非字谜的列表的最大长度,长度为 N,每个字谜单词由 3 个字母的组合组成:'A's、'B's 或'CS。

例如,如果 N = 5:[AAAAA, AAAAB, ..., AABBC, ..., BABAA, ..., CCCCC]。

澄清一下,由于 AAAAB 是 AABAA 的字谜,反之亦然,因此它们从输出列表中被忽略。


我的问题:首先,我想知道如何产生所有 3^5 排列。 我的尝试:

import itertools
print([''.join(p) for p in itertools.combinations_with_replacement('abc', 3)])

>> ['aaaaa', 'aaaab', 'aaaac', 'aaabb', 'aaabc', 'aaacc', 'aabbb', 'aabbc', 'aabcc', 'aaccc', 'abbbb', 'abbbc', 'abbcc', 'abccc', 'acccc', 'bbbbb', 'bbbbc', 'bbbcc', 'bbccc', 'bcccc', 'ccccc']

显然,这份名单远远不够。

我想到了分区,例如)0A、2B、3C 是 0+2+3。在此示例中,手动查找在不到一分钟的时间内给出了 21 的答案。事实上,我通过注意到第三个字母的数量(比如 C,不失一般性)来简化这个过程,取决于 As 和 Bs 的组合,所以我画了一个表格 - 红色叉代表无效组合,因为 sum >= 5: (顺便说一句,我想知道这个想法是如何扩展到 N > 3 的;因为从桌子上看,正方形被切成了两半……)

为了将这个“算法”传输到计算机上,我想到了以某种方式利用对称性——这让我想起了格雷码——但我无法正确实现它。


是否有任何功能可以有效地解决这个问题?然后我什至不必(至少明确地)调用一个字谜检查器函数来比较输入。

【问题讨论】:

itertools.combinations_with_replacement 不能真正解决您想要完成的任务吗?函数的返回基本上是所有的排列,不考虑字谜。 @geany1 但据我了解您的问题,您的预期输出不应包含所有 3^5 个字符串,因为其中一些是彼此的字谜。如果您只想要所有 3^5 个字符串而不需要回避字谜,itertools.product 可以满足您的需求。 如果你不想要字谜,它当然低于 3^5。它缺少 ACACB,因为它已经有 'AABCC',它缺少 BBABC,因为它已经有 "ABBBC"。我真的不明白你,@geany1 @YanOrestes aha 是的,我明白你现在的意思了;对不起,我严重误解了自己的输出! @geany1 很抱歉不够清楚 ;),我很高兴现在解决了! 【参考方案1】:

目标:我正在尝试查找包含非字谜的列表的最大长度,长度为 N,每个字谜单词由 3 个字母的组合组成:'A's、'B's 或'C's。

鉴于此目标,您生成所有3 ** 5 可能字符串然后过滤掉字谜的方法效率不高。您想要的数字可以直接计算而无需实际生成任何字符串:

两个字符串是字谜当且仅当它们具有相同的字母频率。例如,ABCABAABBC 的变位词,因为两个字符串的字母频率均为 'A': 2, 'B': 2, 'C': 1。 因此,不包含字谜的列表的最大可能长度等于可能的不同频率图的数量,其中键是'A', 'B', 'C',值是非负整数,值的总和是 5 (使字符串长度为 5)。 这可以通过计算将非负整数n 划分为k 部分的方式的数量来计算,其中部分的顺序很重要,并且允许部分为0。

这是一个递归解决方案:

from functools import lru_cache

# memoize since there are overlapping subproblems
@lru_cache(maxsize=None)
def count_partitions(n, k):
    if n < 0 or k < 0:
        raise ValueError()
    elif n == 0:
        return 1
    elif k <= 1:
        return k
    else:
        return sum(count_partitions(r, k - 1) for r in range(n + 1))

例子:

>>> count_partitions(5, 3)
21

这与itertools.combinations_with_replacement 一致,在这种情况下会生成一个包含 21 个字符串的列表,并且与您手动计算的结果一致。


事实上,我们可以通过稍微不同的方式来更进一步地构建问题:将n 划分为k 部分的方法数等于在k - 1 分隔符之间插入k - 1 部分的方法数@987654334 @ 项目。放置分隔符的结果是一个长度为n + k - 1的字符串:

在上面的示例中,分隔线的放置方式类似于AA|BB|C。 反之,如果我们知道两个分隔符的位置,比如..|..|.,那么我们可以用字母填充,生成字符串AA|BB|C

因此,我们可以将问题简化为计算在长度为n + k - 1 的字符串中放置k - 1 符号| 的方式的数量。这简直就是二项式系数binom(n + k - 1, k - 1)

def binom(n, k):
    if n < 0 or k < 0 or k > n:
        return 0
    k = min(k, n - k)
    result = 1
    for a, b in zip(range(n, n - k, -1), range(1, k + 1)):
        result *= a
        result //= b
    return result

def count_partitions(n, k):
    return binom(n + k - 1, k - 1)

【讨论】:

以上是关于重复排列 - 非字谜算法的主要内容,如果未能解决你的问题,请参考以下文章

字谜算法返回重复值

算法:按字典顺序在给定索引处查找给定字符串的字谜

查找单词字谜数量的算法?

数据结构和算法爆肝三万字你必须知道的20个解决问题的技巧

数据结构和算法爆肝三万字你必须知道的20个解决问题的技巧

字谜索引计算[重复]