Subcadena Palindrómica Más Larga - LeetCode #5 Daily Challenge

6 min

Subcadena Palindrómica Más Larga — Análisis y explicación

Enunciado del Problema

Dado un string s, retorna la subcadena palindrómica más larga dentro de s. (Una subcadena es palindrómica si se lee igual de izquierda a derecha que de derecha a izquierda).

Análisis Inicial

Comprensión del Problema

El problema nos pide encontrar palíndromos dentro de un string y determinar cuál es el más largo.

Ejemplo 1

  • Input: s = “babad”
  • Output: “bab”
  • Explicación: “aba” también es una respuesta válida.

Ejemplo 2

  • Input: s = “cbbd”
  • Output: “bb”

Restricciones

  • 1 <= s.length <= 1000
  • s consiste solo de dígitos y letras (mayúsculas y minúsculas).

Pista 1: Como se puede reusar un palindromo ya calculado para calcular uno mas largo? Pista 2: Si “aba” es un palindromo, “xabax” lo es? y “xabay” ? Pista 3: Pista basada en la complejidad: Si usamos fuerza bruta y comprobamos si para cada par inicio‑fin un substring es palíndromo, tenemos O(n2)O(n^2) pares inicio‑fin y comprobaciones de palíndromo de O(n)O(n). ¿Podemos reducir el tiempo de comprobación de palíndromos a O(1)O(1) reutilizando algún cálculo previo?

Casos de Prueba Identificados

A continuación se describen los casos relevantes que cubren escenarios típicos y bordes:

  • Caso A — Palíndromo central impar:
// Entrada
s = 'babad'
// Salida esperada (una de las válidas): "bab" o "aba"
  • Caso B — Palíndromo central par:
// Entrada
s = 'cbbd'
// Salida esperada: "bb"
  • Caso C — Todos caracteres iguales:
// Entrada
s = 'aaaa'
// Salida esperada: "aaaa" (todo el string)
  • Caso D — No hay palíndromos largos (solo individuales):
// Entrada
s = 'abcde'
// Salida esperada: cualquiera de los caracteres individuales, p. ej. "a" (longitud 1)
  • Caso E — Palíndromo en los extremos:
// Entrada
s = 'racecarxyz'
// Salida esperada: "racecar"
  • Caso F — Cadena corta / límites:
// Entrada
s = 'a' // longitud mínima
// Salida esperada: "a"

// Entrada
s = 'ab' // sin palíndromo de longitud > 1
// Salida esperada: "a" o "b"

Notas sobre los casos de prueba:

  • Cubren palíndromos de longitud par e impar.
  • Incluyen palíndromos que ocupan todo el string y palíndromos localizados en los extremos.
  • Incluyen casos triviales (longitud 1) y cadenas homogéneas para validar comportamiento óptimo.

Desarrollo de la Solución

Enfoque Elegido

La primera intuición es usar el enfoque de expansión desde el centro: para cada carácter en el string intentamos expandir hacia ambos lados para encontrar el palíndromo más largo posible con ese carácter (o par de caracteres) como centro.

Implementación Paso a Paso

  1. Función de Expansión: Crear una función auxiliar que tome un índice (o dos para el caso par) y expanda hacia ambos lados mientras los caracteres coincidan.
  2. Iterar sobre el String: Para cada índice en el string, llamar a la función de expansión dos veces (una para palíndromos impares y otra para pares).
  3. Actualizar el Máximo: Mantener un registro del palíndromo más largo encontrado durante las expansiones.
  4. Retornar el Resultado: Al final, retornar el substring correspondiente al palíndromo más largo.

Diagramas explicativos

flowchart TD
  subgraph odd["Odd-length center (example: s = 'babad')"]
    S["String: b a b a d"] --> C["Center: i = 1 (a)"]
    C --> Compare1["Compare s[0]='b' with s[2]='b' -> equal"]
    Compare1 --> Result1["Expand -> substring 'bab' (indices 0..2)"]
  end
flowchart TD
  subgraph even["Even-length center (example: s = 'cbbd')"]
    S2["String: c b b d"] --> C2["Center: between i = 1 and i+1 = 2 (b | b)"]
    C2 --> Compare2["Compare s[1]='b' with s[2]='b' -> equal"]
    Compare2 --> Result2["Expand -> substring 'bb' (indices 1..2)"]
  end

Nota: En la práctica, para cada índice i probamos dos expansiones: una considerando i como centro (palíndromo impar) y otra considerando el centro entre i e i+1 (palíndromo par). La expansión compara pares de caracteres equidistantes alrededor del centro y se detiene cuando dejan de coincidir.

Código Completo

/**
 * LeetCode Problem: Longest Palindromic Substring
 * Dificultad: Medium
 * Temas: strings, two-pointers, expansión desde el centro
 *
 * Dado un string s, retorna la subcadena palindrómica más larga dentro de s.
 * Una subcadena es palíndroma si se lee igual de izquierda a derecha que de derecha a izquierda.
 *
 * Ejemplo:
 *   Input: "babad"
 *   Output: "bab" (o "aba", ambas son válidas)
 *
 * Restricciones:
 *   - 1 <= s.length <= 1000
 *   - s consiste solo de letras y/o dígitos (mayúsculas y minúsculas)
 *
 * @param {string} s - Cadena de entrada sobre la que se busca el palíndromo más largo.
 * @returns {string} La subcadena palindrómica más larga encontrada en s.
 */
export function longestPalindromic(s: string): string {
  let maxPalindrome = ''

  // Función auxiliar que expande desde el centro y retorna el palíndromo más largo encontrado
  function expandAroundCenter(left: number, right: number): string {
    while (left >= 0 && right < s.length && s[left] === s[right]) {
      left--
      right++
    }
    return s.slice(left + 1, right)
  }

  for (let i = 0; i < s.length; i++) {
    // Palíndromo impar (centro en i)
    const palindrome1 = expandAroundCenter(i, i)
    // Palíndromo par (centro entre i y i+1)
    const palindrome2 = expandAroundCenter(i, i + 1)

    // Elegimos el más largo de los dos
    const longerPalindrome
      = palindrome1.length > palindrome2.length ? palindrome1 : palindrome2

    // Si es más largo que el máximo actual, lo actualizamos
    if (longerPalindrome.length > maxPalindrome.length) {
      maxPalindrome = longerPalindrome
    }
  }

  return maxPalindrome
}

Análisis de Complejidad

Complejidad Temporal

El algoritmo tiene una complejidad temporal de O(n2)O(n^2), donde nn es la longitud del string. Esto se debe a que para cada uno de los nn índices realizamos expansiones que, en el peor caso, pueden recorrer hasta nn caracteres hacia la izquierda y la derecha. Aunque en la práctica es eficiente para n1000n \leq 1000, sigue siendo cuadrático porque cada expansión puede realizar O(n)O(n) comparaciones.

Complejidad Espacial

La complejidad espacial es O(1)O(1), ya que solo usamos variables fijas (como maxPalindrome y los índices en el bucle), sin estructuras de datos adicionales que crezcan con el tamaño de la entrada.

Casos límite y consideraciones

  • Longitud mínima (n=1): Retorna el único carácter, que es un palíndromo trivial.
  • Si no hay palíndromos largos: Siempre retorna al menos un carácter, ya que cualquier subcadena de longitud 1 es un palíndromo.
  • Cadenas homogéneas: Como “aaaa”, el palíndromo más largo es todo el string.
  • Palíndromos en extremos: El algoritmo los detecta correctamente, como en “racecarxyz”.
  • Sensibilidad a mayúsculas/minúsculas: El problema permite letras y dígitos, pero no especifica insensibilidad a mayúsculas; el código trata ‘A’ y ‘a’ como diferentes, lo cual es correcto según las restricciones.

Reflexiones y Aprendizajes

Conceptos Aplicados

  • Two-pointers: La expansión usa dos punteros que se mueven simétricamente desde el centro.
  • Expansión desde el centro: Técnica eficiente para palíndromos, evitando verificaciones innecesarias.
  • Iteración sistemática: Prueba todos los posibles centros (impares y pares) para garantizar el máximo.

Posibles Optimizaciones

  • Manacher’s Algorithm: Un enfoque más avanzado con O(n)O(n) tiempo usando un array de radios, pero más complejo de implementar. No necesario aquí dado el límite de n=1000n=1000.
  • DP alternativo: Una tabla DP de O(n2)O(n^2) espacio podría usarse, pero es menos eficiente espacialmente y no mejora el tiempo para este problema.

Recursos y Referencias