如何计算将字符串转换为回文所需的字符数?

时间:2010-02-10 13:19:32

标签: algorithm math dynamic-programming recurrence

我最近发现了一个竞赛问题,要求您计算字符串中必须插入的最小字符数(任何位置)以将其转换为回文结构。

例如,给定字符串:“abcbd”我们可以通过插入两个字符将其转换为回文:一个在“a”之后,另一个在“d”之后:“a d bcbd < b> A ”。

这似乎是一个类似问题的概括,要求同样的事情,除了字符只能在最后添加 - 这在使用哈希表的O(N)中有一个非常简单的解决方案。

我一直在尝试修改Levenshtein distance algorithm以解决此问题,但尚未成功。如何解决这个问题(它不一定非常有效,我只对任何DP解决方案感兴趣)将不胜感激。

3 个答案:

答案 0 :(得分:7)

注意:这只是一种好奇心。 Dav提出了一种算法,该算法可以修改为DP算法,以便在O(n ^ 2)时间和O(n ^ 2)空间中轻松运行(也可能是O(n),并且记录更好)。

当然,如果您决定更改允许的操作,这种“天真”算法实际上可能会派上用场。


这是一个'天真'的算法,可以通过巧妙的簿记来加快速度。

给定一个字符串,我们猜测得到的回文的中间位置,然后尝试计算使字符串成为该中间周围的回文所需的插入次数。

如果字符串长度为n,则有2n + 1个可能的middles(每个字符,在两个字符之间,就在字符串之前和之后)。

假设我们考虑一个中间部分,它给我们两个字符串L和R(一个到左边,一个到右边)。

如果我们使用插入,我相信Longest Common Subsequence算法(这是一个DP算法)现在可以用来创建一个'超'字符串,它包含R和R的反向,见{{3} }。

选择中间部分,为您提供最小数量的插入。

我相信这是O(n ^ 3)。 (注意:我没有尝试证明这是真的)。​​

答案 1 :(得分:2)

我的C#解决方案在字符串中查找重复的字符,并使用它们来减少插入次数。在像 program 这样的词中,我使用&#39; r&#39;字符作为边界。在里面,我把它变成了一个回文(递归)。在&#r;之外,我会镜像左侧和右侧的字符。

某些输入有多个最短输出:输出可以 toutptuot outuputuo 。我的解决方案只选择其中一种可能性。

一些示例运行:

  • 雷达 - &gt; 雷达,0插入
  • esystem - &gt; metsystem ,2次插入
  • 消息 - &gt; megassagem ,3次插入
  • stackexchange - &gt; stegnahckexekchangets ,8次插入

首先,我需要检查输入是否已经是回文:

public static bool IsPalindrome(string str)
{
    for (int left = 0, right = str.Length - 1; left < right; left++, right--)
    {
        if (str[left] != str[right])
            return false;
    }
    return true;
}

然后我需要在输入中找到任何重复的字符。可能不止一个。 消息这个词有两个最重复的字符(&#39; e&#39;和&#39; s&#39;):

private static bool TryFindMostRepeatedChar(string str, out List<char> chs)
{
    chs = new List<char>();
    int maxCount = 1;

    var dict = new Dictionary<char, int>();
    foreach (var item in str)
    {
        int temp;
        if (dict.TryGetValue(item, out temp))
        {
            dict[item] = temp + 1;
            maxCount = temp + 1;
        }
        else
            dict.Add(item, 1);
    }

    foreach (var item in dict)
    {
        if (item.Value == maxCount)
            chs.Add(item.Key);
    }

    return maxCount > 1;
}

我的算法在这里:

public static string MakePalindrome(string str)
{
    List<char> repeatedList;
    if (string.IsNullOrWhiteSpace(str) || IsPalindrome(str))
    {
        return str;
    }
    //If an input has repeated characters,
    //  use them to reduce the number of insertions
    else if (TryFindMostRepeatedChar(str, out repeatedList))
    {
        string shortestResult = null;
        foreach (var ch in repeatedList) //"program" -> { 'r' }
        {
            //find boundaries
            int iLeft = str.IndexOf(ch); // "program" -> 1
            int iRight = str.LastIndexOf(ch); // "program" -> 4

            //make a palindrome of the inside chars
            string inside = str.Substring(iLeft + 1, iRight - iLeft - 1); // "program" -> "og"
            string insidePal = MakePalindrome(inside); // "og" -> "ogo"

            string right = str.Substring(iRight + 1); // "program" -> "am"
            string rightRev = Reverse(right); // "program" -> "ma"

            string left = str.Substring(0, iLeft); // "program" -> "p"
            string leftRev = Reverse(left); // "p" -> "p"

            //Shave off extra chars in rightRev and leftRev
            //  When input = "message", this loop converts "meegassageem" to "megassagem",
            //    ("ee" to "e"), as long as the extra 'e' is an inserted char
            while (left.Length > 0 && rightRev.Length > 0 && 
                left[left.Length - 1] == rightRev[0])
            {
                rightRev = rightRev.Substring(1);
                leftRev = leftRev.Substring(1);
            }

            //piece together the result
            string result = left + rightRev + ch + insidePal + ch + right + leftRev;

            //find the shortest result for inputs that have multiple repeated characters
            if (shortestResult == null || result.Length < shortestResult.Length)
                shortestResult = result;
        }

        return shortestResult;
    }
    else
    {
        //For inputs that have no repeated characters, 
        //  just mirror the characters using the last character as the pivot.
        for (int i = str.Length - 2; i >= 0; i--)
        {
            str += str[i];
        }
        return str;
    }
}

请注意,您需要一个反向功能:

public static string Reverse(string str)
{
    string result = "";
    for (int i = str.Length - 1; i >= 0; i--)
    {
        result += str[i];
    }
    return result;
}

答案 2 :(得分:1)

C# 添加到字符串末尾的递归解决方案:

有2个基本案例。当长度为1或2.递归情况:如果极值相等,那么 使palindrome成为没有极端的内部字符串,并将其与极端情况一起返回。 如果极端不相等,则将第一个字符添加到末尾并使其成为回文 内部字符串,包括前一个字符。归还那个。

public static string ConvertToPalindrome(string str) // By only adding characters at the end
    {
        if (str.Length == 1) return str; // base case 1
        if (str.Length == 2 && str[0] == str[1]) return str; // base case 2
        else
        {
            if (str[0] == str[str.Length - 1]) // keep the extremes and call                
                return str[0] + ConvertToPalindrome(str.Substring(1, str.Length - 2)) + str[str.Length - 1];
            else //Add the first character at the end and call
                return str[0] + ConvertToPalindrome(str.Substring(1, str.Length - 1)) + str[0];
        }
    }