如何交错或创建两个字符串的唯一排列(无递归)

Posted

技术标签:

【中文标题】如何交错或创建两个字符串的唯一排列(无递归)【英文标题】:How can I interleave or create unique permutations of two strings (without recursion) 【发布时间】:2012-10-01 22:19:31 【问题描述】:

问题是打印两个给定字符串的所有可能交错。所以我用 Python 写了一个工作代码,运行如下:

def inter(arr1,arr2,p1,p2,arr):
    thisarr = copy(arr)
    if p1 == len(arr1) and p2 == len(arr2):
        printarr(thisarr)
    elif p1 == len(arr1):
        thisarr.extend(arr2[p2:])
        printarr(thisarr)
    elif p2 == len(arr2):
        thisarr.extend(arr1[p1:])
        printarr(thisarr)
    else:
        thisarr.append(arr1[p1])
        inter(arr1,arr2,p1+1,p2,thisarr)
        del thisarr[-1]
        thisarr.append(arr2[p2])
        inter(arr1,arr2,p1,p2+1,thisarr)
    return

它出现在字符串中的每个点,然后对于一次递归调用,它认为当前元素属于第一个数组,而在下一次调用中,它属于另一个数组。因此,如果输入字符串是abcd,它会打印出abcdacbdcdabcabd 等。p1p2 是指向数组的指针(因为 Python字符串是不可变的,我使用的是数组!)。谁能告诉我,这段代码的复杂性是多少,是否可以改进?我编写了一个类似的代码来打印给定数组中长度 k 的所有组合:

def kcomb(arr,i,thisarr,k):
     thisarr = copy(thisarr)
     j,n = len(thisarr),len(arr)
     if n-i<k-j or j >k:
        return
     if j==k:
        printarr(thisarr)
        return
     if i == n:
         return
     thisarr.append(arr[i])
     kcomb(arr,i+1,thisarr,k)
     del thisarr[-1]
     kcomb(arr,i+1,thisarr,k)
     return

这也是基于相同的原理。那么总的来说,如何找到这些功能的复杂性,以及如何优化它们? DP可以做到这些吗?第一个问题的输入输出示例:

>>> arr1 = ['a','b','c']
>>> arr2 = ['d','e','f']
>>> inter(arr1,arr2,0,0,[])
abcdef
abdcef
abdecf
abdefc
adbcef
adbecf
adbefc
adebcf
adebfc
adefbc
dabcef
dabecf
dabefc
daebcf
daebfc
daefbc
deabcf
deabfc
deafbc
defabc

【问题讨论】:

能否请您发布“printarr”的代码 是的,我可以,但这很简单,它只是将数组作为输入,将所有元素连接成一个字符串并打印出来! 很难掌握你在做什么。你能发布输入和预期输出吗? 如果我理解正确 - 你有两个字符串,并且你想查看使用每个字符串中所有字符的所有组合,你可以制作吗? IE。你有两个字符数组,你想以各种可能的方式组合它们。即,您有一个项目列表,并且您想找到组合它们的每种方法? 几乎,但保留单个字符串的顺序。也就是说,如果string1是abcd,string2是efgh,那么在交错的字符串中,a' should come before b, which should come before c, which should come before d`。在这两者之间,来自 string2 的字符应该适合,但在相同的条件下,保持单个字母的顺序。 【参考方案1】:

permutations 适合您吗?或者,这是编码习惯吗?

>>> from itertools import permutations
>>> s1 = "ab"
>>> s2 = "cd"
>>> all_values = [c for c in s1 + s2]
>>> ["".join(r) for r in permutations(all_values)]
['abcd', 'abdc', 'acbd', 'acdb', 'adbc', 'adcb', 'bacd', 'badc', 'bcad', 'bcda', 'bdac', 'bdca', 'cabd', 'cadb', 'cbad', 'cbda', 'cdab', 'cdba', 'dabc', 'dacb', 'dbac', 'dbca', 'dcab', 'dcba']

【讨论】:

不不,我正在尝试开发一个工作代码而不求助于这些内置函数。另外,您的输出不正确,在许多输出字符串中,b 位于 a 之前,而 a 应始终位于 b 之前。那是交错,而不是排列! 我相信这里需要置换输出的一个子集——其中 2 个输入字符串按顺序交错。所以,abdc 不是一个有效的输出 @Cupidvogel 您对使用 itertools 的解决方案完全不感兴趣? @Cupidvogel 你对“求助”内置函数有什么看法? 我想用基本功能自己做。如果我求助于itertools,整个工作都是由它完成的,不是吗?【参考方案2】:

这就是我认为您正在尝试做的事情:

from itertools import product, chain

def interleave(a, b):
    if len(b) > len(a):
        a, b = b, a

    boolean = (True, False)
    for z in range(len(a) - len(b) + 1):
        pairs = list(zip(a[z:], b))
        for vector in product(*[boolean] * max(map(len, (a, b)))):
            permutation = (pair if i else reversed(pair)
                           for i, pair in zip(vector, pairs))
            yield a[:z] + ''.join(chain.from_iterable(permutation))

【讨论】:

你能看看我的解决方案并告诉我复杂性吗?我正在寻找不使用itertools 的非递归解决方案。 好吧,您可以轻松地修改它以不使用 itertools。在这种情况下,product 只是迭代二进制中02^len(a) 之间的所有值,您可能只需执行range(2^len(a)): 并转换为二进制。不过,我刚刚意识到,这段代码遗漏了某些交错。但是,可以对其进行修改以轻松包含它们。我可能会在几个小时后重新访问。【参考方案3】:

您的问题可以简化为创建特定列表的所有唯一排列。假设AB 分别是字符串arr1arr2 的长度。然后构造一个这样的列表:

[0] * A + [1] * B

从这个列表的唯一排列到两个字符串arr1arr2 的所有可能交错存在一一对应(双射)。这个想法是让排列的每个值指定从哪个字符串中获取下一个字符。下面是一个示例实现,展示了如何从排列构造交错:

>>> def make_interleave(arr1, arr2, permutation):
...     iters = [iter(arr1), iter(arr2)]
...     return "".join(iters[i].next() for i in permutation)
... 
>>> make_interleave("ab", "cde", [1, 0, 0, 1, 1])
'cabde'

我在 python 邮件列表中找到了this 问题,该问题询问如何以有效的方式解决此问题。答案建议使用 Knuth 的计算机编程艺术,第 4 卷,分册 2:生成所有排列中描述的算法。我找到了草案here 的在线pdf。该算法也在wikipedia article 中进行了描述。

这是我自己的 next_permutation 算法的注释实现,作为 python 生成器函数。

def unique_permutations(seq):
    """
    Yield only unique permutations of seq in an efficient way.

    A python implementation of Knuth's "Algorithm L", also known from the 
    std::next_permutation function of C++, and as the permutation algorithm 
    of Narayana Pandita.
    """

    # Precalculate the indices we'll be iterating over for speed
    i_indices = list(range(len(seq) - 1, -1, -1))
    k_indices = i_indices[1:]

    # The algorithm specifies to start with a sorted version
    seq = sorted(seq)

    while True:
        yield seq

        # Working backwards from the last-but-one index,           k
        # we find the index of the first decrease in value.  0 0 1 0 1 1 1 0
        for k in k_indices:
            if seq[k] < seq[k + 1]:
                break
        else:
            # Introducing the slightly unknown python for-else syntax:
            # else is executed only if the break statement was never reached.
            # If this is the case, seq is weakly decreasing, and we're done.
            return

        # Get item from sequence only once, for speed
        k_val = seq[k]

        # Working backwards starting with the last item,           k     i
        # find the first one greater than the one at k       0 0 1 0 1 1 1 0
        for i in i_indices:
            if k_val < seq[i]:
                break

        # Swap them in the most efficient way
        (seq[k], seq[i]) = (seq[i], seq[k])                #       k     i
                                                           # 0 0 1 1 1 1 0 0

        # Reverse the part after but not                           k
        # including k, also efficiently.                     0 0 1 1 0 0 1 1
        seq[k + 1:] = seq[-1:k:-1]

根据this 问题,该算法的每个收益的摊销复杂度为 O(1),但根据下面评论的 rici 的说法,只有在所有数字都是唯一的情况下才会出现这种情况,它们肯定不在这种情况。

在任何情况下,产量的数量为时间复杂度提供了一个下限,它由下式给出

(甲+乙)! /(A!* B!)

然后为了找到实时复杂度,我们需要将每个产量的平均复杂度与基于排列构造结果字符串的复杂度相加。如果我们将这个和乘以上面的公式,我们就得到了总时间复杂度。

【讨论】:

@cupidvogel 正如我所说,该算法在代码所在的同一博客中进行了解释。 @lazyr:对于唯一元素序列的排列,该代码仅为 O(1) 摊销。如果序列有很多重复一个值,它变成(我认为)O(n)(对于每个排列)。 (这是 Knuth 分册中的练习 6。) @cupidvogel 我已经用我自己的带注释的重新实现替换了代码。希望它能帮助您理解算法和代码。 不错的算法。只是为了迂腐,交换变量的“最有效”方式(在 python 中)是根本不使用 tmp 变量。您可以直接交换,通过:seq[k], seq[i] = seq[i], seq[k] 我遇到了这个算法的问题:如果我用orig = [1,1,1,2,2,3]; list(unique_permutations(orig)) 测试它,它总是访问原始对象,因此最终的结果是不正确的。也许添加到您可以致电import copy; [copy.copy(x) for x in unique_permutations(orig)] 的帖子中【参考方案4】:

好的,经过一些工作,并使用其他答案的建议。主要是懒惰。 (现在已将其转换为一个类)__all_perms 来自:https://***.com/a/104436/1561176

class Interleave():

    def __init__(self, A, B):
        self.A = A
        self.B = B
        self.results = list(self.__interleave())

    # from https://***.com/a/104436/1561176
    def __all_perms(self, elements):
        if len(elements) <=1:
            yield elements
        else:
            for perm in self.__all_perms(elements[1:]):
                for i in range(len(elements)):
                    #nb elements[0:1] works in both string and list contexts
                    yield perm[:i] + elements[0:1] + perm[i:]

    def __sequences(self):
        return list( sorted( set(
            ["".join(x) for x in self.__all_perms(['a'] * len(self.A) + ['b'] * len(self.B))] ) ) )

    def __interleave(self):
        for sequence in self.__sequences():
            result = ""
            a = 0
            b = 0
            for item in sequence:
                if item == 'a':
                    result+=self.A[a]
                    a+=1
                else:
                    result+=self.B[b]
                    b+=1
            yield result

    def __str__(self):
        return str(self.results)

    def __repr__(self):
        return repr(self.results)

这是用法:

>>> A = ['a', 'b', 'c']
>>> B = ['d', 'e', 'f']
>>> Interleave(A, B)
['abcdef', 'abdcef', 'abdecf', 'abdefc', 'adbcef', 'adbecf', 'adbefc', 'adebcf', 'adebfc', 'adefbc', 'dabcef', 'dabecf', 'dabefc', 'daebcf', 'daebfc', 'daefbc', 'deabcf', 'deabfc', 'deafbc', 'defabc']

此外,您还可以访问类成员,例如:

>>> inter = Interleave(A, B)
>>> inter.results
['abcdef', 'abdcef', 'abdecf', 'abdefc', 'adbcef', 'adbecf', 'adbefc', 'adebcf', 'adebfc', 'adefbc', 'dabcef', 'dabecf', 'dabefc', 'daebcf', 'daebfc', 'daefbc', 'deabcf', 'deabfc', 'deafbc', 'defabc']
>>> inter.A
['a', 'b', 'c']
>>> inter.B
['d', 'e', 'f']

【讨论】:

@Cupidvogel 好吧,all_perms() 来自一个答案:***.com/a/104436/1561176 所以我不会在这里开始讨论它,因为它在那里讨论过。 _sequences() 只需调用all_perms(),然后将结果列表转换为字符串,使其唯一,并对它们进行排序,然后将它们转换为列表并将它们发送到interleave()interleave() 读取列表并简单地添加来自@ 的值987654330@ 或 B 取决于它从序列中给出的顺序。 请注意,此算法会生成 all 排列,然后过滤掉重复项,这与我的答案中的算法不同。例如,对于两个长度为 10 的字符串,__all_perms 产生 2432902008176640000 次,而 next_permutation 只产生必要的 184756 次。 @lazyr 是的,你是对的。在这种情况下,最好结合使用两个答案。当我写这个答案时,我没有看到您编写的代码,只有您提供的有人遇到类似问题的链接以及您制作独特组合的想法。我想我没有考虑过缩放。

以上是关于如何交错或创建两个字符串的唯一排列(无递归)的主要内容,如果未能解决你的问题,请参考以下文章

无重复全排列_非递归实现

如何处理java中的递归Stack Overflows

97. 交错字符串-7月18日

没有 itertools 的两个值的排列(使用递归!)

无重复全排列

php 递归01 利用递归实现按字典顺序全排列