Partizionamento palindromico II
Combini una tabella dei palindromi precalcolata con la DP 1D per trovare il numero minimo di tagli necessari a suddividere una stringa in palindromi.
Partizionamento palindromico II è una lezione Coding Interview Prep gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Coding Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Coding Interview Prep include 4 lezioni in totale.
Problema: numero minimo di tagli per il partizionamento
Palindrome Partitioning II chiede di trovare, data una stringa s, il numero minimo di tagli necessari affinché ogni sottostringa della partizione sia un palindromo. Per 'aab', un taglio produce ['aa', 'b'], quindi la risposta è 1. Per 'a' la risposta è 0 (è già un palindromo). Questo problema combina due fasi di DP: prima si precalcola quali sottostringhe sono palindromi, poi si usa una DP 1D per trovare il numero minimo di tagli.
Fase 1: precalcolo della tabella dei palindromi
Per prima cosa si costruisce is_pal[i][j] = True se s[i..j] è un palindromo, usando la DP su intervalli. Questa procedura richiede O(n²) di tempo e O(n²) di spazio. In alternativa, l'espansione attorno al centro riempie la stessa tabella in O(n²) di tempo. Questa tabella è necessaria perché la DP 1D dei tagli interrogherà ripetutamente is_pal[i][j]: il precalcolo evita di ripetere i controlli dei palindromi all'interno del ciclo della DP dei tagli.
def build_palindrome_table(s):
n = len(s)
is_pal = [[False]*n for _ in range(n)]
for i in range(n):
is_pal[i][i] = True
for i in range(n-1):
is_pal[i][i+1] = (s[i] == s[i+1])
for length in range(3, n+1):
for i in range(n-length+1):
j = i + length - 1
is_pal[i][j] = (s[i] == s[j]) and is_pal[i+1][j-1]
return is_pal
print(build_palindrome_table('aab'))Configurazione della DP 1D dei tagli
Si definisca cuts[i] come il numero minimo di tagli per partizionare s[0..i]. Se s[0..i] è già un palindromo, cuts[i] = 0. Altrimenti si prova ogni divisione: per ogni j da 0 a i-1, se s[j+1..i] è un palindromo, allora cuts[i] = min(cuts[i], cuts[j] + 1). La domanda è: cosa succede se l'ultimo pezzo della partizione è s[j+1..i]? In tal caso servono cuts[j] tagli per il prefisso, più un ulteriore taglio.
def min_cut(s):
n = len(s)
is_pal = build_palindrome_table(s)
cuts = [float('inf')] * n
for i in range(n):
if is_pal[0][i]:
cuts[i] = 0 # entire prefix is a palindrome
else:
for j in range(i):
if is_pal[j+1][i]:
cuts[i] = min(cuts[i], cuts[j] + 1)
return cuts[n-1]Soluzione completa e analisi passo per passo
Analizziamo 'aab'. Tabella dei palindromi: is_pal[0][0]='a'=T, is_pal[1][1]='a'=T, is_pal[2][2]='b'=T, is_pal[0][1]='aa'=T, is_pal[1][2]='ab'=F, is_pal[0][2]='aab'=F. Tagli: cuts[0]=0 ('a' è un palindromo), cuts[1]=0 ('aa' è un palindromo), cuts[2]: 'aab' non è un palindromo; si prova j=1: is_pal[2][2]=T, quindi cuts[2] = cuts[1]+1 = 1. Risposta: 1.
def build_palindrome_table(s):
n = len(s)
is_pal = [[False]*n for _ in range(n)]
for i in range(n):
is_pal[i][i] = True
for i in range(n-1):
is_pal[i][i+1] = (s[i] == s[i+1])
for length in range(3, n+1):
for i in range(n-length+1):
j = i + length - 1
is_pal[i][j] = (s[i] == s[j]) and is_pal[i+1][j-1]
return is_pal
def min_cut(s):
n = len(s)
is_pal = build_palindrome_table(s)
cuts = [float('inf')] * n
for i in range(n):
if is_pal[0][i]:
cuts[i] = 0
else:
for j in range(i):
if is_pal[j+1][i]:
cuts[i] = min(cuts[i], cuts[j] + 1)
return cuts[n-1]
print(min_cut('aab')) # 1
print(min_cut('ababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab'))Complessità temporale e spaziale
La fase 1 (tabella dei palindromi) richiede O(n²) di tempo e O(n²) di spazio. La fase 2 (DP dei tagli) ha un ciclo esterno sulle n posizioni e un ciclo interno sugli n punti di divisione, quindi richiede anch'essa O(n²) di tempo. Complessivamente: O(n²) di tempo, O(n²) di spazio. Lo spazio può essere ridotto a O(n) per l'array dei tagli, ma la tabella dei palindromi richiede comunque O(n²). Nei colloqui ci si aspetta O(n²): una soluzione O(n) che usa l'algoritmo di Manacher va oltre l'ambito tipico.
Espansione attorno al centro per la tabella dei palindromi
Anziché usare l'approccio della DP su intervalli per la tabella dei palindromi, è possibile riempire is_pal usando l'espansione attorno al centro. Per ogni posizione centrale, si procede verso l'esterno e si segnano tutti i palindromi trovati. La procedura richiede comunque O(n²) di tempo e O(n²) di spazio, ma nella pratica potrebbe essere più veloce grazie a un migliore comportamento della cache. Entrambi gli approcci sono validi nei colloqui.
def build_pal_expand(s):
n = len(s)
is_pal = [[False]*n for _ in range(n)]
def expand(l, r):
while l >= 0 and r < n and s[l] == s[r]:
is_pal[l][r] = True
l -= 1; r += 1
for i in range(n):
expand(i, i) # odd-length centres
expand(i, i+1) # even-length centres
return is_pal
print('Expand-around-centre palindrome table built')Enumerazione di tutte le partizioni (Parte I)
Palindrome Partitioning I (un problema correlato) chiede di enumerare TUTTE le partizioni valide in cui ogni sottostringa è un palindromo. Si usa il backtracking, con la tabella dei palindromi precalcolata come criterio di potatura. A differenza della DP dei tagli minimi, che conta le soluzioni, questo approccio ne enumera un numero esponenziale e richiede una strategia completamente diversa.
def partition_all(s):
n = len(s)
is_pal = build_pal_expand(s)
result = []
def backtrack(start, path):
if start == n:
result.append(path[:])
return
for end in range(start, n):
if is_pal[start][end]:
path.append(s[start:end+1])
backtrack(end+1, path)
path.pop()
backtrack(0, [])
return result
print(partition_all('aab')) # [['a','a','b'], ['aa','b']]Inizializzare cuts con n-1
Un trucco comune consiste nell'inizializzare cuts[i] = i anziché inf, poiché nel caso peggiore per s[0..i] si separa ogni carattere, ottenendo i tagli. In questo modo non è necessario verificare la presenza di inf nel codice. Quando is_pal[0][i] è vero, il valore viene sostituito con 0. Questa inizializzazione chiarisce il limite superiore del numero di tagli e semplifica leggermente il codice.
def min_cut_clean(s):
n = len(s)
is_pal = build_palindrome_table(s)
cuts = list(range(n)) # cuts[i] = i (worst case)
for i in range(n):
if is_pal[0][i]:
cuts[i] = 0
else:
for j in range(1, i+1):
if is_pal[j][i]:
cuts[i] = min(cuts[i], cuts[j-1] + 1)
return cuts[n-1]Alternativa: DP in un solo passaggio senza tabella separata
Una variante elegante riempie contemporaneamente la tabella dei palindromi e la DP dei tagli. Mentre si espandono i palindromi a partire da ciascun centro, si aggiorna immediatamente l'array cuts. Per un palindromo s[l..r], è possibile aggiornare cuts[r] = min(cuts[r], (cuts[l-1]+1 if l > 0 else 0)). Si evita così un passaggio separato O(n²) sulla tabella e l'implementazione potrebbe risultare più semplice durante un colloquio con poco tempo a disposizione.
Casi limite da considerare
Principali casi limite per Palindrome Partitioning II: (1) una stringa di un solo carattere restituisce 0 tagli; (2) una stringa che è già un palindromo restituisce 0 tagli; (3) una stringa con tutti caratteri distinti richiede n-1 tagli; (4) una stringa composta dallo stesso carattere ripetuto (ad esempio 'aaaa') richiede 0 tagli, poiché l'intera stringa è un palindromo. Verifichi sempre che la soluzione gestisca correttamente l'uscita anticipata is_pal[0][i] = True.
def build_palindrome_table(s):
n = len(s)
is_pal = [[False]*n for _ in range(n)]
for i in range(n):
is_pal[i][i] = True
for i in range(n-1):
is_pal[i][i+1] = (s[i] == s[i+1])
for length in range(3, n+1):
for i in range(n-length+1):
j = i + length - 1
is_pal[i][j] = (s[i] == s[j]) and is_pal[i+1][j-1]
return is_pal
def min_cut(s):
n = len(s)
is_pal = build_palindrome_table(s)
cuts = list(range(n))
for i in range(n):
if is_pal[0][i]:
cuts[i] = 0
else:
for j in range(1, i+1):
if is_pal[j][i]:
cuts[i] = min(cuts[i], cuts[j-1] + 1)
return cuts[n-1]
print(min_cut('a')) # 0
print(min_cut('aaaa')) # 0
print(min_cut('abc')) # 2Consigli per comunicare durante il colloquio
Quando presenta questo problema in un colloquio, inizi dall'approccio in due fasi: prima costruisca la tabella dei palindromi, quindi esegua una DP 1D sull'array dei tagli. Spieghi a parole la ricorrenza prima di scrivere il codice. Menzioni che la tabella dei palindromi contiene O(n²) elementi e che ciascuno viene calcolato in O(1) usando la ricorrenza della DP su intervalli. Prima di scrivere la soluzione completa, ripercorra sempre l'esempio di traccia per dimostrare la correttezza anche sotto pressione.
Verifica rapida
Verifichi la Sua comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep trattati in questa lezione.
Riepilogo della lezione
In questa lezione ha imparato: il palindrome partitioning II usa due fasi di DP: prima si precalcola la tabella dei palindromi, poi si esegue la DP 1D dei tagli, la ricorrenza dei tagli è cuts[i] = min(cuts[j-1] + 1) per ogni j per cui s[j..i] è un palindromo e la complessità complessiva è O(n²) di tempo e O(n²) di spazio. Nel prossimo argomento affronteremo il problema Burst Balloons, che usa un ingegnoso approccio di DP inversa su intervalli.
Impara Coding Interview Prep con un tutor IA — gratis
Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.
- Corsi
- 90
- Lezioni
- 360
Domande Frequenti
La lezione «Partizionamento palindromico II» è gratuita?
Sì — il testo completo di «Partizionamento palindromico II» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Coding Interview Prep, passa a CoddyKit PRO. Il corso Coding Interview Prep include 4 lezioni in totale.
Cosa imparerò in «Partizionamento palindromico II»?
Combini una tabella dei palindromi precalcolata con la DP 1D per trovare il numero minimo di tagli necessari a suddividere una stringa in palindromi. Eserciti Coding Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Coding Interview Prep?
Non è richiesta alcuna esperienza precedente. Coding Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Partizionamento palindromico II»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Coding Interview Prep?
Sì. Ogni lezione Coding Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Schema della DP sugli intervalli e ordine di riempimento
- Sottosequenza e sottostringa palindroma più lunga
- Partizionamento palindromico II
- Burst Balloons: DP sugli intervalli al contrario