一系列字符串的最长公共子序列

时间:2012-12-26 18:21:41

标签: string algorithm optimization data-structures

对于2个字符串的Longest Common Subsequence,我在网上找到了很多例子,我相信我理解这个解决方案。
我不明白的是,为N字符串应用此问题的正确方法是什么?以某种方式应用相同的解决方案?怎么样?解决方案有何不同?什么?

2 个答案:

答案 0 :(得分:4)

当输入具有任意数量的字符串时,此问题变为NP-hard。当输入具有固定数量的字符串时,此问题变得易于理解 。如果输入有k个字符串,我们可以使用k维数组来应用相同的DP技术来存储子问题的最优解。

参考:Longest common subsequence problem

答案 1 :(得分:1)

要查找2个字符串A和B的最长公共子序列(LCS),您可以对角遍历2维数组,如您发布的Link所示。数组中的每个元素都对应于找到子串A的LCS的问题。和B' (按行号剪切,B按列号剪切)。可以通过计算数组中所有元素的值来解决此问题。 您必须确定在计算数组元素的值时,计算该给定值所需的所有子问题都已解决。这就是你沿对角线遍历二维数组的原因。

这个解决方案可以缩放以找到N个字符串之间最长的公共子序列,但是这需要一种通用的方法来迭代N个维度的数组,这样只有当元素需要解决方案的所有子问题时才能到达任何元素已经解决了。

您可以递归地解决问题,而不是以特殊顺序迭代N维数组。通过递归,保存中间解决方案非常重要,因为许多分支机构需要相同的中间解决方案。我在C#中写了一个小例子:

string lcs(string[] strings)
{
    if (strings.Length == 0)
        return "";
    if (strings.Length == 1)
        return strings[0];
    int max = -1;
    int cacheSize = 1;
    for (int i = 0; i < strings.Length; i++)
    {
        cacheSize *= strings[i].Length;
        if (strings[i].Length > max)
            max = strings[i].Length;
    }
    string[] cache = new string[cacheSize];
    int[] indexes = new int[strings.Length];
    for (int i = 0; i < indexes.Length; i++)
        indexes[i] = strings[i].Length - 1;
    return lcsBack(strings, indexes, cache);
}
string lcsBack(string[] strings, int[] indexes, string[] cache)
{
    for (int i = 0; i < indexes.Length; i++ )
        if (indexes[i] == -1)
            return "";
    bool match = true;
    for (int i = 1; i < indexes.Length; i++)
    {
        if (strings[0][indexes[0]] != strings[i][indexes[i]])
        {
            match = false;
            break;
        }
    }
    if (match)
    {
        int[] newIndexes = new int[indexes.Length];
        for (int i = 0; i < indexes.Length; i++)
            newIndexes[i] = indexes[i] - 1;
        string result = lcsBack(strings, newIndexes, cache) + strings[0][indexes[0]];
        cache[calcCachePos(indexes, strings)] = result;
        return result;
    }
    else
    {
        string[] subStrings = new string[strings.Length];
        for (int i = 0; i < strings.Length; i++)
        {
            if (indexes[i] <= 0)
                subStrings[i] = "";
            else
            {
                int[] newIndexes = new int[indexes.Length];
                for (int j = 0; j < indexes.Length; j++)
                    newIndexes[j] = indexes[j];
                newIndexes[i]--;
                int cachePos = calcCachePos(newIndexes, strings);
                if (cache[cachePos] == null)
                    subStrings[i] = lcsBack(strings, newIndexes, cache);
                else
                    subStrings[i] = cache[cachePos];
            }
        }
        string longestString = "";
        int longestLength = 0;
        for (int i = 0; i < subStrings.Length; i++)
        {
            if (subStrings[i].Length > longestLength)
            {
                longestString = subStrings[i];
                longestLength = longestString.Length;
            }
        }
        cache[calcCachePos(indexes, strings)] = longestString;
        return longestString;
    }
}
int calcCachePos(int[] indexes, string[] strings)
{
    int factor = 1;
    int pos = 0;
    for (int i = 0; i < indexes.Length; i++)
    {
        pos += indexes[i] * factor;
        factor *= strings[i].Length;
    }
    return pos;
}

我的代码示例可以进一步优化。缓存的许多字符串都是重复的,有些是重复的,只添加了一个附加字符。当输入字符串变大时,这会占用比所需更多的空间。

输入时:"666222054263314443712", "5432127413542377777", "6664664565464057425"

返回的LCS为"54442"