DSA Interview Prep · Lezione

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.

Lezione 3 di 413 passaggi

Partizionamento palindromico II è una lezione DSA 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 DSA Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DSA 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'))   # 2

Consigli 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.

Gratis per iniziare

Impara Python 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
30
Lezioni
120

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 DSA Interview Prep, passa a CoddyKit PRO. Il corso DSA 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 DSA 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 DSA Interview Prep?

Non è richiesta alcuna esperienza precedente. DSA 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 DSA Interview Prep?

Sì. Ogni lezione DSA 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

  1. Schema della DP sugli intervalli e ordine di riempimento
  2. Sottosequenza e sottostringa palindroma più lunga
  3. Partizionamento palindromico II
  4. Burst Balloons: DP sugli intervalli al contrario
← Torna a DSA Interview Prep