Longest Palindromic Substring
Ti viene data una stringa s composta da lettere inglesi minuscole. Restituisci la sua sottostringa palindroma più lunga: la sequenza più lunga di lettere consecutive che si legge allo stesso modo da sinistra a destra e da destra a sinistra. Se più sottostringhe hanno la stessa lunghezza massima, restituisci quella che inizia più a sinistra.
Funzione
- sstring
- la stringa minuscola da cercare
- Restituiscestring
- la sottostringa palindroma più lunga di s, quella più a sinistra in caso di parità
Vincoli
1 ≤ s.length ≤ 2000scontiene solo lettere inglesi minuscole.- Quando diversi palindromi hanno la lunghezza massima, la risposta è quello con l’indice iniziale più piccolo.
Esempi
- Input
- s = "bananas"
- Output
- "anana"
- Spiegazione
"anana"si legge allo stesso modo da entrambe le estremità e ha 5 lettere. Nessuna sequenza più lunga funziona:"banana"inizia con b e finisce con a,"ananas"inizia con a e finisce con s, e la parola intera inizia con b e finisce con s.
- Input
- s = "xyzzyabba"
- Output
- "yzzy"
- Spiegazione
"yzzy"e"abba"sono entrambi palindromi di lunghezza 4, e non esiste nulla di più lungo."yzzy"inizia all'indice 1, prima di"abba"all'indice 5, quindi vince in caso di parità.
- Input
- s = "abcd"
- Output
- "a"
- Spiegazione
- Non ci sono due lettere uguali, quindi ogni palindromo è composto da una sola lettera. Quello più a sinistra è
"a".
+18 test nascosti all’invio
Per approfondire
Riesci a trovare la risposta in tempo O(n)?
Suggerimenti
Aprili uno alla volta. Ognuno rivela un po’ di più.
Ogni palindromo è simmetrico rispetto al suo centro. Osserva
"aba"e"abba": dov'è il centro di ciascuno e quanti centri possibili ha una stringa di lunghezza n?Posizionati al centro. Se le lettere su entrambi i lati coincidono, hai un palindromo più lungo di due lettere rispetto a prima. Quando devi smettere di allungarlo, e perché nessun palindromo più lungo può avere lo stesso centro?
Per ciascuno dei
2n-1centri (ogni lettera e ogni spazio tra due lettere adiacenti), espanditi verso l’esterno finché le lettere corrispondono e tieni a mente il risultato più lungo. Sostituisci il migliore solo quando un nuovo palindromo è strettamente più lungo, così, in caso di parità, vince quello più a sinistra.
Soluzione
Un palindromo si specchia attorno al suo centro, che può essere una sola lettera (lunghezza dispari, come "anana") oppure lo spazio tra due lettere uguali (lunghezza pari, come "abba"). Controllare ogni sottostringa separatamente ignora questa struttura e richiede O(n³). Espandere ogni palindromo verso l’esterno a partire dal suo centro riutilizza ogni confronto, riducendo la ricerca a un tempo O(n²) con O(1) di memoria aggiuntiva.
Controlla ogni sottostringa
Corretto, ma non termina sui test più grandi
Intuizione
Una sottostringa è definita dal suo primo indice i e dal suo ultimo indice j. Verificala con due puntatori: confronta s[i] con s[j], poi s[i+1] con s[j-1], e così via, fermandoti alla prima differenza. Se i puntatori si incontrano o si incrociano senza trovare differenze, la sottostringa è un palindromo. Conserva la più lunga che trovi.
Per la regola in caso di parità, procedi con gli indici iniziali da sinistra a destra e sostituisci la migliore solo quando un nuovo palindromo è strettamente più lungo. Un palindromo successivo della stessa lunghezza non sostituirà mai uno precedente, quindi restituirai quello più a sinistra.
Questo esamina tutte le n(n+1)/2 sottostringhe, quindi non può non trovare la risposta. È lento perché ogni verifica può scorrere metà della sottostringa. Per una stringa di 2000 copie di a, ogni sottostringa è un palindromo e ogni verifica arriva fino al centro: circa n³/12 ≈ 6.7 × 10^8 confronti tra lettere.
Algoritmo
- Inizia con la prima lettera come migliore: inizio 0, lunghezza 1.
- Per ogni inizio
ie ogni finej ≥ i, confronta le lettere da entrambe le estremità verso il centro finché non sono diverse o i puntatori si incontrano. - Se i puntatori si sono incontrati senza trovare differenze,
s[i..j]è un palindromo. - Se la sua lunghezza
j-i+1supera la migliore, registraie quella lunghezza. - Restituisci la sottostringa che inizia dall’inizio migliore e ha la lunghezza migliore.
def longestPalindrome(s):
n = len(s)
best_start, best_len = 0, 1
for i in range(n):
for j in range(i, n):
# Compare s[i..j] from both ends toward the middle
left, right = i, j
while left < right and s[left] == s[right]:
left += 1
right -= 1
is_palindrome = left >= right
if is_palindrome and j - i + 1 > best_len:
best_start, best_len = i, j - i + 1
return s[best_start:best_start + best_len]Tabella dei palindromi per lunghezza
Intuizione
La forza bruta dimentica ciò che ha imparato. Quando esamina "anana", confronta a con a e poi n con n: il secondo confronto è l'intero test di "nan", che ha già eseguito. La regola che evita questo lavoro: s[i..j] è un palindromo quando i suoi due estremi coincidono e la parte compresa tra loro, s[i+1..j-1], è un palindromo. Un confronto e una risposta memorizzata bastano per determinare ogni sottostringa.
Memorizza le risposte in una tabella pal[i][j] e riempila in base alla lunghezza. Ogni singola lettera è un palindromo. Una sottostringa di due lettere lo è quando le due lettere coincidono. Per le lunghezze maggiori, applica la regola: la parte interna è più corta di due lettere, quindi la sua cella è già stata riempita.
In "bananas", pal[1][5] ("anana") è vero perché s[1] e s[5] sono entrambe a, e pal[2][4] ("nan") è vero. Le lunghezze aumentano e gli indici iniziali procedono da sinistra a destra, quindi il primo palindromo di una nuova lunghezza record è anche quello più a sinistra di quella lunghezza. Circa n²/2 celle richiedono O(1) ciascuna, quindi il tempo è O(n²); il costo è la memoria: 4 × 10^6 celle per n = 2000.
Algoritmo
- Crea una tabella n × n
pal, tutta impostata su false. - Per ogni lunghezza da 1 a n e ogni posizione iniziale
iil cui estremoj = i+length-1resta all'interno della stringa, controlla le due lettere alle estremità. - Contrassegna
pal[i][j]quando corrispondono e la lunghezza è al massimo 2 oppurepal[i+1][j-1]è true. - Quando la lunghezza di una cella contrassegnata supera quella migliore, registra
ie la lunghezza. - Restituisci la sottostringa che inizia dalla posizione iniziale migliore.
def longestPalindrome(s):
n = len(s)
# pal[i][j] is True when s[i..j] reads the same both ways
pal = [[False] * n for _ in range(n)]
best_start, best_len = 0, 1
for length in range(1, n + 1):
for i in range(n - length + 1):
j = i + length - 1
# Equal ends, and the part inside them is a palindrome (or too short to matter)
if s[i] == s[j] and (length <= 2 or pal[i + 1][j - 1]):
pal[i][j] = True
if length > best_len:
best_start, best_len = i, length
return s[best_start:best_start + best_len]Espandi attorno a ogni centro
Intuizione
Ogni palindromo ha un centro. Uno di lunghezza dispari, come "anana", è centrato su una lettera; uno di lunghezza pari, come "abba", è centrato sullo spazio tra le due lettere centrali. Una stringa di lunghezza n ha n lettere e n-1 spazi, quindi 2n-1 centri possibili.
Partendo da un centro, procedi verso l'esterno di una lettera per lato finché le due lettere coincidono. Ogni passo dimostra l'esistenza di un palindromo più lungo di due lettere. La prima discordanza, oppure il bordo della stringa, interrompe l'esplorazione: nessun palindromo più lungo può avere lo stesso centro, perché includerebbe la coppia di lettere discordanti. Quindi, un'unica esplorazione verso l'esterno trova il palindromo più lungo attorno a ciascun centro, e il più lungo tra questi è la risposta.
In "bananas", parti dalla lettera a all'indice 3. Le lettere agli indici 2 e 4 sono entrambe n, quelle agli indici 1 e 5 sono entrambe a, e quelle agli indici 0 e 6 sono b e s, quindi l'esplorazione si ferma con lunghezza 5. L'inizio è 3 - (5-1)/2 = 1, che dà "anana". La stessa formula, center - (length-1)/2 arrotondata per difetto, funziona anche per i centri negli spazi.
Esplora i centri da sinistra a destra e sostituisci il migliore solo quando trovi una lunghezza strettamente maggiore. Due palindromi della stessa lunghezza hanno la stessa parità e quello con il centro più a sinistra inizia prima, quindi vince quello più a sinistra. Il caso peggiore è una stringa composta da una sola lettera ripetuta: da ciascun centro si procede fino al bordo più vicino, per circa n²/2 = 2 × 10^6 passi quando n = 2000; la memoria richiesta è di pochi interi.
Algoritmo
- Scrivi
expand(left, right): mentre entrambi gli indici sono all'interno della stringa e le lettere corrispondono, decrementalefte incrementaright. Restituisciright-left-1. - Per ogni centro da 0 a n-1, prendi il valore maggiore tra
expand(center, center)eexpand(center, center+1). - Se quella lunghezza supera la migliore, imposta l'inizio migliore su
center - (length-1)/2, arrotondato per difetto, e la lunghezza migliore su quel valore. - Restituisci la sottostringa all'inizio migliore con la lunghezza migliore.
def expand(s, left, right):
# Grow outward while the two ends match; return the palindrome's length
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return right - left - 1
def longestPalindrome(s):
best_start, best_len = 0, 1
for center in range(len(s)):
# Odd lengths grow from one letter, even lengths from the gap after it
length = max(expand(s, center, center), expand(s, center, center + 1))
if length > best_len:
best_start = center - (length - 1) // 2
best_len = length
return s[best_start:best_start + best_len]
Trappole e casi limite
L’idea è breve, quindi i bug si nascondono nei dettagli: i centri pari, la lunghezza dopo l’espansione, la regola per i pareggi e lo slicing.
- Espandere solo attorno alle lettere non individua nessun palindromo di lunghezza pari. Con
"abba"restituisce"a"invece di"abba". - L’espansione si ferma un passo oltre ciascuna estremità, quindi il palindromo è
s[left+1..right-1]con lunghezzaright-left-1. Usareright-left+1aggiunge due lettere che non corrispondono. - Sostituire il migliore in caso di lunghezza uguale restituisce il palindromo più a destra:
"abba"invece di"yzzy"per"xyzzyabba". - Per un centro tra due caratteri,
center - length/2è troppo a sinistra di una posizione. In"xyzzyabba"lo spazio dopo l’indice 2 ha lunghezza 4 e l’inizio è2 - (4-1)/2 = 1, non 0. - Le API per lo slicing differiscono: C++
substre C#Substringaccettano una lunghezza, mentre JavaScriptsubstringe Javasubstringaccettano un indice finale. - Nella tabella, riempire le righe partendo dall’inizio 0 fa leggere
pal[i+1][j-1]prima che sia stato riempito. Riempila in base alla lunghezza oppure percorri gli inizi a ritroso.
Domande frequenti4
Qual è la complessità temporale della sottostringa palindroma più lunga?
L’espansione attorno ai centri richiede un tempo O(n²) e O(1) di memoria aggiuntiva. Anche l’approccio con tabella richiede un tempo O(n²), ma necessita di O(n²) di memoria, mentre controllare ogni sottostringa richiede un tempo O(n³). L’algoritmo di Manacher raggiunge O(n), ma raramente gli intervistatori se lo aspettano.
Perché l’espansione dal centro usa 2n-1 centri?
Un palindromo di lunghezza dispari ha una lettera centrale, mentre uno di lunghezza pari ha uno spazio centrale tra due lettere uguali. Una stringa di n lettere ha n lettere e n-1 spazi tra lettere adiacenti. Espandersi partendo solo dalle lettere fa perdere palindromi come "abba".
Che cos'è l'algoritmo di Manacher?
Trova il palindromo più lungo intorno a ogni centro in un tempo totale O(n). Mantiene il palindromo che arriva più a destra fino a quel momento e, se un centro si trova al suo interno, parte dalla risposta del suo centro speculare, così nessuna lettera viene confrontata di nuovo da zero. Vale la pena conoscerlo per nome; l’espansione attorno al centro è la soluzione che gli intervistatori di solito si aspettano.
In che modo la sottostringa palindroma più lunga è diversa dalla sottosequenza palindroma più lunga?
Una sottostringa è una sequenza di lettere consecutive, mentre una sottosequenza può saltare delle lettere. In "character" la sottostringa palindroma più lunga è "ara", ma "carac" è una sottosequenza palindroma di lunghezza 5. La variante della sottosequenza si risolve con una tabella su (i, j) che scarta un estremo quando i due estremi sono diversi.
Problemi simili
Problemi che usano le stesse idee. Risolverne due o tre è ciò che fissa uno schema.
Python
def longestPalindrome(s):
# Scrivi il codice quiCaso 1
Caso 2
Caso 3
Input
s = "bananas"
Atteso
"anana"