字符串中回文子序列的总数

时间:2015-03-04 12:00:39

标签: algorithm data-structures dynamic-programming

问题是这样的 -

对于作为输入给出的每个字符串,您需要告诉它作为回文的子序列的数量(不一定是不同的)。请注意,空字符串不是回文。 例如," aab"的回文子序列。是:

" a"," a"," b"," aa",方法返回4.

我有动态编程解决方案,以便找到最长的回文子序列,因此试图从中获取想法。无法真正得到解决方案。可能甚至不需要动态编程。建议请。

还有一个问题。当条件"不一定是不同的"被移除,我们仍然可以计算而不实际生成所有回文子序列吗?

3 个答案:

答案 0 :(得分:13)

[编辑19/10/2015:匿名审稿人指出公式存在问题,这促使我注意到另一个甚至更大的错误......现在已修复。]

我现在看到如何将解决方案时间缩短到 O(n ^ 2)。我会留下我的另一个答案,以防它作为这个的踏脚石有趣。注意:这也是(也)只是问题第一部分的解决方案;我认为没有办法只有效地计算不同的回文子序列(PS)。

不要计算在i和j位置开始和结束的PS的数量,而是计算从开始或在之后开始并在之前结束的数量学家称之为g(i,j)。

我们可以尝试写g(i,j)= g(i,j-1)+ g(i + 1,j)+(x [i] == x [j])* g(i + 1,j-1)对于j> 1的情况一世。但这并不常用,因为前两个术语将重复计算任何在 i之后开始并在 j之前结束的PS。

关键的见解是要注意我们可以通过减去g()的其他值来轻松计算在某个精确位置开始或结束的PS的数量,并且可能添加更多的值g()重新开始以补偿重复计算。例如,从完全 i开始并在完全 j结束的PS的数量是g(i,j) - g(i + 1,j) - g( i,j-1)+ g(i + 1,j-1):最后一项纠正了第二项和第三项都计算所有g(i + 1,j-1)PS开始的事实在之后,在 j之前结束

在i之后或之后开始且在j之前或之前结束的每个PS恰好是4个类别中的1个:

  1. 它在我之后开始,在j之前结束。
  2. 从i开始,到j之前结束。
  3. 从i开始,到j结束。
  4. 从i开始,到j结束。
  5. g(i + 1,j)计算类别1或3中的所有PS,并且g(i,j-1)计算类别1或2中的所有PS,因此它们的和g(i + 1,j)+ g(i,j-1)对每个类别2或3中的所有PS进行计数,并对类别1中的所有PS进行两次计数。因为g(i + 1,j-1)仅对类别1中的所有PS进行计数,所以将其减去得到g(i + 1,j)+ g(i,j-1)-g(i + 1,j-) 1)给出类别1,2和3中PS的总数。剩余的PS是类别4中的PS。如果x [i]!= x [j]则该类别中没有PS;否则,就像在i + 1之后或之后开始并且在j-1之前或之后结束的PS一样多,即g(i + 1,j-1),加上一个额外的2字符序列x [I]×[J]。 [编辑:感谢评论者Tuxdude在这里进行了2次修复!]

    有了这个,我们可以用一种方式表达g(),将二次情形从f()变为恒定时间:

    g(i, i) = 1 (i.e. when j = i)
    g(i, i+1) = 2 + (x[i] == x[i+1]) (i.e. 3 iff adjacent chars are identical, otherwise 2)
    g(i, j) = 0 when j < i (this new boundary case is needed)
    g(i, j) = g(i+1, j) + g(i, j-1) - g(i+1, j-1) + (x[i] == x[j])*(g(i+1, j-1)+1) when j >= i+2
    

    最后的答案现在只是g(1,n)。

答案 1 :(得分:1)

这是一个可怕的O(n ^ 4)解决方案:

每个回文子序列从某个位置i开始并在某个位置结束j> = i使得x [i] = x [j],并且其“内部”(除了第一个和最后一个之外的所有字符)都是空的或x [i + 1 .. j-1]的回文子序列。

因此我们可以将f(i,j)定义为从i开始并以j> = i结束的回文子序列的数量。然后

f(i, j) = 0 if x[i] != x[j]
f(i, i) = 1 (i.e. when j = i)
f(i, j) = 1 + the sum of f(i', j') over all i < i' <= j' < j otherwise

[编辑:修正以计算长度<= 2的回文子序列!]

然后最终答案是f(i,j)在所有1&lt; = i&lt; = j&lt; = n上的总和。

这个的DP是O(n ^ 4)因为有n ^ 2个表项,并且计算每个表项需要O(n ^ 2)时间。 (通过利用x [i]!= x [j]暗示f(i,j)= 0的事实,可能有可能将其加速到至少O(n ^ 3)。)

答案 2 :(得分:0)

使用DP的直观O(n ^ 3)解决方案:

让每个状态dp(i,j)表示字符串[i ... j]中回文子序列的数量 那么简单的递归公式就是

for k in range i, j-1:
    if(A[j]==A[k]){
        dp(i,j) = dp(i,j) + dp(k+1,j-1);

理念很简单..添加新字符检查它是否是子序列的结束。如果在先前计算的较小子问题中存在相同的字符,则它添加范围(k + 1,j-1)中包含的子序列的数量。 只需处理角落案件。 添加一个作为新添加的字符也是单个字符子序列。 即使范围(k + 1,j-1)中没有子序列,您仍然会得到1个长度为2的新子序列(如“aa”)。