最常见的回文序列

时间:2012-09-04 19:54:21

标签: string algorithm lcs

是否有任何有效的算法可以计算两个给定字符串的最长公共回文子序列的长度?

例如:

字符串1. afbcdfca

字符串2. bcadfcgyfka

LCPS为5,LCPS字符串为afcfa

3 个答案:

答案 0 :(得分:5)

只需使用LCS算法来两个以上的序列

如果我没弄错的话,那么

 LCS( afbcdfca, acfdcbfa, bcadfcgyfka, akfygcfdacb) = afcfa

由你决定字符串#2和#4。

更新:不,这是一个反例:LCS(aba,aba,bab,bab)= ab,ba。 LCS不能确保子序列是回文,你可能需要添加这个约束。无论如何,LCS的递推方程是一个很好的起点。

如果你以生成器样式实现LCS,那么它生成长度为n的所有LCS,然后是n-1,n-2等,那么你应该能够相当有效地计算LCS-gen中最长的公共成员(string1,reverse-string1),LCS-gen(string2,reverse-string2)。但是,如果有一个高效的LCS-gen,我还没有检查过。

答案 1 :(得分:2)

这是我的万无一失的逐行演练,因为这是非常常见的,大部分时间人们将70%的动态编程部分解释并停留在血腥细节上。

1)最佳子结构: 设X[0..n-1]为长度为n的输入序列,L(0, n-1)X[0..n-1].

的最长回文子序列的长度

如果X的最后和第一个字符相同,则为L(0, n-1) = L(1, n-2) + 2。等等,为什么,如果第二个和第二个到最后一个字符不是 同样,最后和第一次同样不会没用。不,这个“后续”不一定是连续的。

/* Driver program to test above functions */
int main()
{
    char seq[] = "panamamanap";
    int n = strlen(seq);
    printf ("The lnegth of the LPS is %d", lps(seq, 0, n-1));
    getchar();
    return 0;
}

int lps(char *seq, int i, int j)
{   
    // Base Case 1: If there is only 1 character   
    if (i == j)     
        return 1;    

    // Base Case 2: If there are only 2 characters and both are same   
    if (seq[i] == seq[j] && i + 1 == j)     
        return 2;    

    // If the first and last characters match   
    if (seq[i] == seq[j])      
        return lps (seq, i+1, j-1) + 2;    

    // If the first and last characters do not match   
    else return max( lps(seq, i, j-1), lps(seq, i+1, j) );
}

考虑到上面的实现,以下是长度为6且具有所有不同字符的序列的部分递归树。

               L(0, 5)
             /        \ 
            /          \  
        L(1,5)          L(0,4)
       /    \            /    \
      /      \          /      \
  L(2,5)    L(1,4)  L(1,4)  L(0,3)

在上面的部分递归树中,L(1, 4)被解决了两次。如果我们绘制完整的递归树,那么我们可以看到有 许多子问题一次又一次地得到解决。像其他典型的动态编程(DP)问题一样,重新计算相同的子问题 可以通过自下而上的方式构建临时数组L[][]来避免。

//返回seq

中最长的回文子序列的长度
int lps(char *str)
{
   int n = strlen(str);
   int i, j, cl;
   int L[n][n];  // Create a table to store results of subproblems


   // Strings of length 1 are palindrome of length 1
   for (i = 0; i < n; i++)
      L[i][i] = 1;

    for (cl=2; cl<=n; cl++)                              //again this is the length of chain we are considering
    {
        for (i=0; i<n-cl+1; i++)                         //start at i
        {
            j = i+cl-1;                                  //end at j
            if (str[i] == str[j] && cl == 2)             //if only 2 characters and they are the same then set L[i][j] = 2
               L[i][j] = 2;
            else if (str[i] == str[j])                   //if greater than length 2 and first and last characters match, add 2 to the calculated value of the center stripped of both ends
               L[i][j] = L[i+1][j-1] + 2;
            else
               L[i][j] = max(L[i][j-1], L[i+1][j]);      //if not match, then take max of 2 possibilities
        }
    }

    return L[0][n-1];
}

所以这就像逻辑上的非动态编程一样,只是在这里我们将结果保存在一个数组中,所以我们不会一遍又一遍地计算相同的东西

答案 2 :(得分:0)

与此问题相同: http://code.google.com/codejam/contest/1781488/dashboard#s=p2

http://code.google.com/codejam/contest/1781488/dashboard#s=a&a=2

在下面的代码中,我给你一个cd(s)方法(它返回一个char dict,它告诉你字符串中的下一个或前一个char是等于该char的位置)。使用它来实现动态编程解决方案,我也为您提供了示例代码。

使用动态编程时,只有len(s1)* len(s1)/ 2状态,因此可以使用order(N ^ 2)。

我们的想法是从s1获取一个char或不接受它。 如果你将char取下s1,你必须从前面和后面(s1和s2)取下它。如果你不接受它,你继续前进1.(这意味着s2的状态与s1的状态保持同步,因为你总是贪婪地从两者的外部 - 所以你只担心s1可以有多少个状态)。

此代码可以帮助您完成大部分工作。 cd1(char dict 1)帮助你找到s1中的下一个字符,它等于你所在的字符(向前和向后)。

在递归的solve()方法中,你需要决定start1,end1 ..等应该是什么。每次拿一个角色时加总2(除非start1 == end1 - 然后加1)

s1 = "afbcdfca"
s2 = "bcadfcgyfka"

def cd(s):
    """returns dictionary d where d[i] = j where j is the next occurrence of character i"""
    char_dict = {}
    last_pos = {}
    for i, char in enumerate(s):
        if char in char_dict:
            _, forward, backward = char_dict[char]
            pos = last_pos[char]
            forward[pos] = i
            backward[i] = pos
            last_pos[char] = i
        else:
            first, forward, backward = i, {}, {}
            char_dict[char] = (first, forward, backward)
            last_pos[char] = i
    return char_dict

print cd(s1)
"""{'a': ({0: 7}, {7: 0}), 'c': ({3: 6}, {6: 3}), 'b': ({}, {}), 'd': ({}, {}), 'f': ({1: 5}, {5: 1})}"""

cd1, cd2 = cd(s1), cd(s2)

cache = {}
def solve(start1, end1, start2, end2):
    state = (start1, end1)
    answer = cache.get(state, None)
    if answer:
        return answer

    if start1 < end1:
        return 0
    c1s, c1e = s1[start1], s1[end1]
    c2s, c2e = s2[start2], s2[end2]

    #if any of c1s, c1e, c2s, c2e are equal and you don't take, you must
    #skip over those characters too:
    dont_take_end1 = solve(start1, end1 - 1, start2, end2)

    do_take_end1 = 2
    if do_take_end1:
        end1_char = s1[end1]
        #start1 = next character along from start1 that equals end1_char
        #end1 = next char before end1 that equals end1_char
        #end2 = next char before end2 that..
        #start2 = next char after .. that ..
        do_take_end1 += solve(start1, end1, start2, end2)


    answer = cache[state] = max(dont_take_end1, do_take_end1)
    return answer

print solve(0, len(s1), 0, len(s2))
相关问题