2010-02-10 19 views
18

Recientemente encontré un problema de concurso que le pide que calcule el número mínimo de caracteres que se deben insertar (en cualquier lugar) en una cadena para convertirlo en un palíndromo.¿Cómo puedo calcular el número de caracteres necesarios para convertir una cadena en un palíndromo?

Por ejemplo, dada la cadena: "ABCBD" podemos convertirlo en un palíndromo mediante la inserción de sólo dos personajes: uno después de "a" y otro después de "d": "un d bcbd un".

Esto parece ser una generalización de un problema similar que pide lo mismo, excepto que los caracteres solo se pueden agregar al final; esto tiene una solución bastante simple en O (N) usando tablas hash.

He estado tratando de modificar el Levenshtein distance algorithm para resolver este problema, pero no se han realizado correctamente. Cualquier ayuda sobre cómo resolver esto (no necesariamente tiene que ser eficiente, solo estoy interesado en cualquier solución de DP) sería apreciada.

+0

@IVlad - excelente pregunta. He eliminado el bit de introducción y he añadido un enlace al artículo de Wikipedia sobre Levenshtein. Espero que esté bien. Bienvenido a Stack Overflow! –

+0

Me olvidé de preguntar, ¿es esta tarea? –

+0

No es tarea. Fue un problema dado en una olimpíada de ciencias de la computación en mi país hace unos años, solo me interesaba la solución ya que no podía encontrar una oficial ni encontrar un DP por mi cuenta. – IVlad

Respuesta

7

Nota: Esto es solo una curiosidad. Dav propuso un algoritmo que se puede modificar al algoritmo DP para ejecutarse en tiempo O (n^2) y O (n^2) espacio fácilmente (y tal vez O (n) con una mejor contabilidad).

Por supuesto, este algoritmo "ingenuo" podría ser útil si decide cambiar las operaciones permitidas.


Aquí hay un algoritmo ingenuo, que probablemente puede hacerse más rápido con la contabilidad inteligente.

Dada una cadena, adivinamos el centro del palíndromo resultante y luego tratamos de calcular el número de inserciones necesarias para que la cadena sea un palíndromo alrededor de ese medio.

Si la cadena es de longitud n, hay 2n + 1 posibles medios (Cada carácter, entre dos caracteres, justo antes y justo después de la cadena).

Supongamos que consideramos un medio que nos da dos cadenas L y R (una hacia la izquierda y otra hacia la derecha).

Si estamos usando insertos, creo que el algoritmo de Longest Common Subsequence (que es un algoritmo DP) se puede utilizar ahora el crear una cadena de 'super' que contiene tanto L y reverso de R, ver Shortest common supersequence.

Elija el centro que le proporcione las inserciones de número más pequeño.

Esto es O (n^3) Creo. (Nota: no he intentado probar que es cierto).

+0

¿Cómo se usa el SCS para averiguar cuántas inserciones se necesitan si se centra el palíndromo en un personaje k, por ejemplo? Para mi ejemplo: "abcbd". Supongamos que nos centramos en la segunda "b". SCS entre "abc" y "d" es "abcd". ¿Cómo te dice esto cuántos caracteres necesitas insertar? Estoy pensando que es 2 * strlen (SCS) - strlen (L) - strlen (R), ¿es correcto? – IVlad

+0

Sí, eso se ve bien. –

1

C#solución recursiva añadiendo al final de la cadena:

Hay 2 casos base. Cuando la longitud es 1 o 2. Caso recursivo: si los extremos son iguales, entonces hacen que el palíndromo sea el hilo interno sin los extremos y lo devuelve con los extremos. Si los extremos no son iguales, agregue el primer carácter al final y haga palindrome la cadena interior incluyendo el último carácter anterior. devuelve eso.

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

Mi solución de C# busca caracteres repetidos en una cadena y los utiliza para reducir el número de inserciones. En una palabra como programa, utilizo los caracteres 'r' como un límite. Dentro de las 'r's, hago de eso un palíndromo (recursivamente). Fuera de las 'r's, hago un espejo de los personajes de la izquierda y la derecha.

Algunas entradas tienen más de una salida más corta: de salida puede ser toutptuot o outuputuo. Mi solución solo selecciona una de las posibilidades.

Algunos ejemplo ejecuta:

  • radar ->radar, 0 inserciones
  • esystem ->metsystem, 2 inserciones
  • mensaje ->megassagem , 3 inserciones
  • StackExchange ->stegnahckexekchangets, 8 inserciones

En primer lugar tengo que comprobar si hay una entrada que ya es un palíndromo:

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

entonces necesito encontrar caracteres repetidos en la entrada . Puede haber más de uno. La palabra mensaje tiene dos personajes más repetidos ('e' y '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; 
} 

Mi algoritmo es aquí:

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

Tenga en cuenta que se necesita una función inversa:

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