二进制数组的唯一子序列

时间:2017-10-06 14:54:18

标签: algorithm math

是否有一种有效的方法来计算位数组的可能子序列数?

从左到右读取数组,可能省略一些元素。不允许重复的子序列。

当数组大小增加时,强制执行所有可能的子序列需要很长时间。

1 个答案:

答案 0 :(得分:2)

这个简单的线性时间算法取自"Algorithms for subsequence combinatorics" by Cees Elzinga et al. (2008),略有修改,因为数学往往是1索引的,但Python是0索引的。它适用于任何序列s,而不仅仅是二进制序列:

def count_unique_subsequences(s):
    """Returns the number of unique subsequences of the sequence s"""
    L = {} 
    N = []
    count = 1
    for c in s:
        N.append(count)
        count *= 2
        if c in L:
            count -= N[L[c] - 1]
        L[c] = len(N)
    return count

这是一个动态编程解决方案,它迭代地计算当前字符串的每个前缀的唯一子序列的数量。所有这些子序列仍然是下一个前缀的子序列,另外我们可以添加任何用下一个字符扩展的子序列,除了那些上次遇到相同字符时没有扩展的子序列。 (因为在那一点上,我们计算了使用该字符扩展的所有子序列。)在此算法中,向量N维护每个连续前缀s的唯一子序列的计数(由长度为{前缀),而L跟踪每个字符最后一次出现的索引。

在考虑了这段代码之后,我意识到N真的是多余的;我们需要它的唯一原因是能够查找对应于当前字符的子序列计数。但我们可以直接将该计数存储到L中,而不是存储第二个表查找的索引。这不会改变算法的时间复杂度(虽然它会略微加快)但它确实将空间复杂度降低到O(|Σ|),其中Σ是字母表。对于二进制序列,这使算法成为线性时间/恒定空间。这是修改后的算法:

def count_unique_subsequences(s):
    """Returns the number of unique subsequences of the sequence s"""
    L = {}
    count = 1
    for c in s:
        adds = count - L.get(c, 0)
        L[c] = count
        count += adds
    return count

如上所述,该函数计算枚举中未出现的空子序列,因此您可能希望从最终结果中减去一个。

在许多其他有趣的结果中,Elzinga论文还考虑了给定大小的字母表的最大唯一子序列计数,证明最大计数是广义Fibonacci序列。对于字母大小2,最大计数可以计算为:

max_count(0) = 1
max_count(1) = 2
max_count(n) = max_count(n - 2) + max_count(n - 1) + 1

fibonacci(n+2)-1

生成最大模式的字符串由循环重复的字母组成。

因此,实际上列举所有唯一子序列必须采用指数时间,因为(可能)存在指数数量的此类序列。但是,指数(对于二进制序列)是φ,小于2。