Как я могу вычислить количество символов, необходимых для превращения строки в палиндром?

Недавно я нашел задачу на конкурс, в которой вас просят вычислить минимальное количество символов, которое нужно вставить (в любом месте) в строку, чтобы превратить ее в палиндром.

Например, при наличии строки: "abcbd" мы можем превратить ее в палиндром, вставив всего два символа: один после "a", а другой после "d": "adbcbda ".

Это похоже на обобщение аналогичной проблемы, которая требует того же, за исключением того, что символы можно добавлять только в конце - это имеет довольно простое решение в O (N) с использованием хэш-таблиц.

Я пытался изменить алгоритм расстояния Левенштейна, чтобы решить эту проблему, но безуспешно. . Любая помощь в том, как решить эту проблему (она не обязательно должна быть эффективной, меня просто интересует любое решение DP), будет оценена по достоинству.


person IVlad    schedule 10.02.2010    source источник
comment
@IVlad - отличный вопрос. Я удалил вводную часть и добавил ссылку на статью в Википедии о Левенштейне. Надеюсь, это нормально. Добро пожаловать в Stack Overflow!   -  person Dominic Rodger    schedule 10.02.2010
comment
Я забыл спросить, это домашнее задание?   -  person    schedule 10.02.2010
comment
Не домашнее задание. Это была задача, заданная на олимпиаде по информатике в моей стране несколько лет назад, меня просто интересовало решение, так как я не мог найти официальную задачу и самостоятельно придумать DP.   -  person IVlad    schedule 10.02.2010
comment
См. мой ответ на очень похожий вопрос здесь: stackoverflow.com/a/18758991/2771718 Он использует подход DP.   -  person Piotr Kołaczkowski    schedule 13.09.2013


Ответы (3)


Примечание: это просто любопытство. Дэв предложил алгоритм, который можно преобразовать в алгоритм DP, чтобы он легко работал за время O(n^2) и пространство O(n^2) (и, возможно, за O(n) с лучшей бухгалтерией).

Конечно, этот «наивный» алгоритм может оказаться полезным, если вы решите изменить разрешенные операции.


Вот «наивный» алгоритм, который, вероятно, можно ускорить с помощью умной бухгалтерии.

Учитывая строку, мы угадываем середину результирующего палиндрома, а затем пытаемся вычислить количество вставок, необходимых для превращения строки в палиндром вокруг этой середины.

Если строка имеет длину n, существует 2n+1 возможных средних (каждый символ, между двумя символами, непосредственно перед и сразу после строки).

Предположим, мы рассматриваем середину, которая дает нам две цепочки L и R (одну слева и одну справа).

Если мы используем вставки, я полагаю, что алгоритм самой длинной общей подпоследовательности (который является алгоритмом DP) может теперь можно использовать для создания «супер» строки, которая содержит как L, так и обратную R, см. Кратчайшая общая суперпоследовательность.

Выберите середину, которая дает вам наименьшее число вставок.

Я считаю, что это O (n ^ 3). (Примечание: я не пытался доказать, что это правда).

person Community    schedule 10.02.2010
comment
Как вы используете SCS, чтобы узнать, сколько вставок необходимо, если вы центрируете палиндром, например, вокруг символа k? Для моего примера: abcbd. Предположим, мы центрируемся на втором b. SCS между abc и d есть abcd. Как это говорит вам, сколько символов вам нужно вставить? Я думаю, что это 2 * strlen (SCS) - strlen (L) - strlen (R), это правильно? - person IVlad; 10.02.2010

Мое решение C# ищет повторяющиеся символы в строке и использует их для уменьшения количества вставок. В таком слове, как program, я использую символы 'r' в качестве границы. Внутри 'r' я делаю палиндром (рекурсивно). За пределами «р» я отражаю символы слева и справа.

Некоторые входы имеют более одного кратчайшего выхода: output может быть toutptuot или outuputuo. Мое решение выбирает только одну из возможностей.

Некоторый пример работает:

  • radar -> radar, 0 вставок
  • esystem -> metsystem, 2 вставки
  • message -> megassagem, 3 вставки
  • stackexchange -> 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;
}

Затем мне нужно найти повторяющиеся символы во входных данных. Их может быть больше одного. Слово message имеет два наиболее часто повторяющихся символа ("e" и "s"):

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;
    }
}

Обратите внимание, что вам нужна функция Reverse:

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

C# Рекурсивное решение с добавлением в конец строки:

Есть 2 базовых случая. Когда длина равна 1 или 2. Рекурсивный случай: если крайние значения равны, сделать палиндром внутренней строкой без крайних значений и вернуть ее с крайними значениями. Если крайние значения не равны, то добавьте первый символ в конец и сделайте палиндром внутренней строкой, включающей предыдущий последний символ. вернуть это.

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];
        }
    }
person Ernesto Cejas    schedule 16.12.2012