O algoritmo de Manacher resolve o problema de encontrar a maior substring palindromica em tempo linear O(n). Um palindromo e uma string que se le da mesma forma da esquerda para a direita e da direita para a esquerda (como “arara” ou “reviver”). Enquanto a abordagem de expandir ao redor de cada centro custa O(n^2) no pior caso, Manacher aproveita informacoes de palindromos ja descobertos para evitar comparacoes redundantes.
O algoritmo e notavel por sua elegancia: usa um truque de transformacao para tratar palindromos de comprimento par e impar de forma uniforme, e mantem uma “fronteira direita” que avanca monotonicamente pelo texto. Foi publicado por Glenn Manacher em 1975 e permanece como a solucao otima para este problema classico.
Como Funciona
O Truque da Transformacao
Para tratar uniformemente palindromos pares e impares, inserimos um caractere separador (ex.: ‘#’) entre cada caractere e nas extremidades:
Original: "abba"
Transformada: "#a#b#b#a#"
Palindromo impar "aba": -> "#a#b#a#" (centro no 'b')
Palindromo par "abba": -> "#a#b#b#a#" (centro no '#' entre os b's)
Agora TODOS os palindromos na string transformada sao impares!
Array P (Raio do Palindromo)
Para cada posicao i na string transformada, P[i] armazena o raio do maior palindromo centrado em i:
Transformada: # a # b # b # a #
Indice: 0 1 2 3 4 5 6 7 8
P: 0 1 0 1 4 1 0 1 0
Verificacao de P[4]=4:
Centro: indice 4 (caractere '#')
Palindromo: T[0..8] = "#a#b#b#a#"
Raio 4: 4 caracteres para cada lado
Corresponde ao palindromo original "abba" (comprimento = P[4] = 4)
O Algoritmo: Espelho e Fronteira
A sacada do Manacher: ao calcular P[i], usamos palindromos ja descobertos:
String: # a # b # a # b # a #
0 1 2 3 4 5 6 7 8 9 10
Suponha que ja calculamos ate i=7 e encontramos:
Centro C=5, Borda direita R=10
P = [0, 1, 0, 3, 0, 5, 0, ?, ?, ?, ?]
Para i=7:
Espelho de i em relacao a C: espelho = 2*C - i = 2*5 - 7 = 3
P[espelho] = P[3] = 3
i esta dentro da borda R (i < R=10)
Logo: P[i] >= min(P[espelho], R - i) = min(3, 10-7) = 3
Diagrama:
# a # b # a # b # a #
0 1 2 3 4 5 6 7 8 9 10
|<--- C=5 --->| R=10
|esp| | i |
3 7
O palindromo ao redor de i=7 espelha o palindromo ao redor de i=3
(ambos estao dentro do palindromo grande centrado em C=5)
P[7] comeca com 3 e tenta expandir alem (mas nao consegue)
P[7] = 3
Se P[i] + i > R, atualizamos C=i e R=P[i]+i (nova fronteira)
Trace Completo
Texto original: "abaaba"
Transformada T: "#a#b#a#a#b#a#"
0 1 2 3 4 5 6 7 8 9 10 11 12
C=0, R=0
i=1: R=0, i>=R -> expande manualmente
T[0]=='#'==T[2]? sim -> P[1]=1
T[-1]? nao -> P[1]=1
C=1, R=2
i=2: espelho=0, min(P[0], R-2)=min(0,0)=0
expande: nada -> P[2]=0
i=3: i>R=2 -> expande manualmente
T[2]=='#'==T[4]? sim -> P[3]=1
T[1]=='a'==T[5]? sim -> P[3]=2
T[0]=='#'==T[6]? sim -> P[3]=3
T[-1]? nao -> P[3]=3
C=3, R=6
i=4: espelho=2, min(P[2], R-4)=min(0,2)=0
expande: nada -> P[4]=0
i=5: espelho=1, min(P[1], R-5)=min(1,1)=1
T[3]=='b'==T[7]? nao -> P[5]=1
(nao atualiza C,R pois 5+1=6=R)
i=6: espelho=0, min(P[0], R-6)=min(0,0)=0
expande: T[5]=='a'==T[7]? sim -> P[6]=1
T[4]=='#'==T[8]? sim -> P[6]=2
T[3]=='b'==T[9]? sim -> P[6]=3
T[2]=='#'==T[10]? sim -> P[6]=4
T[1]=='a'==T[11]? sim -> P[6]=5
T[0]=='#'==T[12]? sim -> P[6]=6
C=6, R=12
...continuando...
P = [0, 1, 0, 3, 0, 1, 6, 1, 0, 3, 0, 1, 0]
Maior P[i] = 6, na posicao 6
Palindromo na string original: centro = (6-1)/2 = 2 (... nao, melhor calcular)
inicio_original = (6 - P[6]) / 2 = (6 - 6) / 2 = 0
comprimento = P[6] = 6
palindromo = "abaaba"
Complexidade
| Caso | Tempo | Espaco |
|---|---|---|
| Melhor | O(n) | O(n) |
| Medio | O(n) | O(n) |
| Pior | O(n) | O(n) |
A borda direita R nunca diminui e avanca no maximo n vezes no total. Cada posicao e visitada no maximo uma vez na expansao, garantindo O(n) amortizado. O espaco O(n) e para a string transformada e o array P.
Implementacao em Rust
/// Encontra a maior substring palindromica usando o algoritmo de Manacher.
/// Retorna a substring palindromica e sua posicao no texto original.
fn manacher(texto: &str) -> (String, usize) {
let bytes = texto.as_bytes();
let n = bytes.len();
if n == 0 {
return (String::new(), 0);
}
// Constroi a string transformada com separadores '#'
// "#a#b#c#" para "abc"
let mut t: Vec<u8> = Vec::with_capacity(2 * n + 1);
for &b in bytes {
t.push(b'#');
t.push(b);
}
t.push(b'#');
let t_len = t.len();
let mut p = vec![0usize; t_len]; // raio do palindromo em cada posicao
let mut centro = 0usize; // centro do palindromo com maior borda direita
let mut borda_dir = 0usize; // borda direita desse palindromo
for i in 0..t_len {
// Usa o espelho se estamos dentro da borda direita
if i < borda_dir {
let espelho = 2 * centro - i;
p[i] = p[espelho].min(borda_dir - i);
}
// Tenta expandir alem do valor inicial
let mut esq = i as isize - p[i] as isize - 1;
let mut dir = i + p[i] + 1;
while esq >= 0 && dir < t_len && t[esq as usize] == t[dir] {
p[i] += 1;
esq -= 1;
dir += 1;
}
// Atualiza centro e borda se expandimos alem
if i + p[i] > borda_dir {
centro = i;
borda_dir = i + p[i];
}
}
// Encontra o maior palindromo
let mut max_raio = 0;
let mut max_centro = 0;
for i in 0..t_len {
if p[i] > max_raio {
max_raio = p[i];
max_centro = i;
}
}
// Converte de volta para coordenadas do texto original
let inicio = (max_centro - max_raio) / 2;
let comprimento = max_raio;
(texto[inicio..inicio + comprimento].to_string(), inicio)
}
/// Encontra TODOS os palindromos centrados em cada posicao.
/// Retorna um vetor de (inicio, comprimento) para cada palindromo maximal.
fn todos_palindromos(texto: &str) -> Vec<(usize, usize)> {
let bytes = texto.as_bytes();
let n = bytes.len();
if n == 0 {
return Vec::new();
}
let mut t: Vec<u8> = Vec::with_capacity(2 * n + 1);
for &b in bytes {
t.push(b'#');
t.push(b);
}
t.push(b'#');
let t_len = t.len();
let mut p = vec![0usize; t_len];
let mut centro = 0usize;
let mut borda_dir = 0usize;
for i in 0..t_len {
if i < borda_dir {
let espelho = 2 * centro - i;
p[i] = p[espelho].min(borda_dir - i);
}
let mut esq = i as isize - p[i] as isize - 1;
let mut dir = i + p[i] + 1;
while esq >= 0 && dir < t_len && t[esq as usize] == t[dir] {
p[i] += 1;
esq -= 1;
dir += 1;
}
if i + p[i] > borda_dir {
centro = i;
borda_dir = i + p[i];
}
}
// Coleta palindromos com comprimento >= 2
let mut palindromos = Vec::new();
for i in 0..t_len {
if p[i] >= 2 {
let inicio = (i - p[i]) / 2;
let comprimento = p[i];
palindromos.push((inicio, comprimento));
}
}
palindromos
}
fn main() {
let texto = "abaaba";
println!("Texto: \"{}\"", texto);
println!();
// Maior palindromo
let (palindromo, posicao) = manacher(texto);
println!(
"Maior palindromo: \"{}\" (posicao {}, comprimento {})",
palindromo,
posicao,
palindromo.len()
);
println!();
// Todos os palindromos
let todos = todos_palindromos(texto);
println!("Todos os palindromos (comprimento >= 2):");
for (inicio, comprimento) in &todos {
println!(
" posicao {}: \"{}\"",
inicio,
&texto[*inicio..*inicio + *comprimento]
);
}
println!();
// Mais exemplos
let exemplos = vec![
"racecar",
"babad",
"cbbd",
"forgeeksskeegfor",
];
println!("Mais exemplos:");
for ex in &exemplos {
let (pal, pos) = manacher(ex);
println!(" \"{}\" -> \"{}\" (pos {})", ex, pal, pos);
}
}
Exemplo de Execucao
Texto: "abaaba"
Maior palindromo: "abaaba" (posicao 0, comprimento 6)
Todos os palindromos (comprimento >= 2):
posicao 0: "aba"
posicao 0: "abaaba"
posicao 2: "aa"
posicao 3: "aba"
Mais exemplos:
"racecar" -> "racecar" (pos 0)
"babad" -> "bab" (pos 0)
"cbbd" -> "bb" (pos 1)
"forgeeksskeegfor" -> "geeksskeeg" (pos 3)
Visualizacao dos palindromos em “abaaba”:
a b a a b a
0 1 2 3 4 5
Palindromos encontrados:
[a b a] . . . "aba" (centro=1, raio=1)
[a b a a b a] "abaaba" (centro=2.5, raio=3)
. . [a a] . . "aa" (centro=2.5, raio=1)
. . . [a b a] "aba" (centro=4, raio=1)
Variacoes e Otimizacoes
1. Abordagem O(n^2): Expandir ao Redor do Centro (para Comparacao)
/// Encontra o maior palindromo expandindo ao redor de cada centro.
/// Complexidade: O(n^2) no pior caso, mas simples de implementar.
fn maior_palindromo_expansao(texto: &str) -> (String, usize) {
let bytes = texto.as_bytes();
let n = bytes.len();
if n == 0 {
return (String::new(), 0);
}
let mut melhor_inicio = 0;
let mut melhor_comprimento = 1;
// Funcao auxiliar para expandir ao redor de um centro
let expandir = |mut esq: isize, mut dir: usize| -> (usize, usize) {
while esq >= 0 && dir < n && bytes[esq as usize] == bytes[dir] {
esq -= 1;
dir += 1;
}
let inicio = (esq + 1) as usize;
let comprimento = dir - inicio;
(inicio, comprimento)
};
for i in 0..n {
// Palindromo impar (centro em i)
let (inicio, comp) = expandir(i as isize, i);
if comp > melhor_comprimento {
melhor_inicio = inicio;
melhor_comprimento = comp;
}
// Palindromo par (centro entre i e i+1)
if i + 1 < n {
let (inicio, comp) = expandir(i as isize, i + 1);
if comp > melhor_comprimento {
melhor_inicio = inicio;
melhor_comprimento = comp;
}
}
}
(
texto[melhor_inicio..melhor_inicio + melhor_comprimento].to_string(),
melhor_inicio,
)
}
2. Contar Total de Substrings Palindromicas
/// Conta o numero total de substrings palindromicas (incluindo repeticoes).
fn contar_palindromos(texto: &str) -> usize {
let bytes = texto.as_bytes();
let n = bytes.len();
if n == 0 {
return 0;
}
// Constroi string transformada
let mut t: Vec<u8> = Vec::with_capacity(2 * n + 1);
for &b in bytes {
t.push(b'#');
t.push(b);
}
t.push(b'#');
let t_len = t.len();
let mut p = vec![0usize; t_len];
let mut centro = 0;
let mut borda_dir = 0;
for i in 0..t_len {
if i < borda_dir {
let espelho = 2 * centro - i;
p[i] = p[espelho].min(borda_dir - i);
}
let mut esq = i as isize - p[i] as isize - 1;
let mut dir = i + p[i] + 1;
while esq >= 0 && dir < t_len && t[esq as usize] == t[dir] {
p[i] += 1;
esq -= 1;
dir += 1;
}
if i + p[i] > borda_dir {
centro = i;
borda_dir = i + p[i];
}
}
// Cada P[i] no indice impar (caractere original) contribui com
// (P[i] + 1) / 2 palindromos impares.
// Cada P[i] no indice par (#) contribui com P[i] / 2 palindromos pares.
let mut total = 0usize;
for i in 0..t_len {
if i % 2 == 1 {
// Indice impar = caractere original
total += (p[i] + 1) / 2; // inclui o proprio caractere (palindromo de tamanho 1)
} else {
// Indice par = separador '#'
total += p[i] / 2;
}
}
// Subtrai os palindromos de comprimento 1 se nao quiser conta-los
// total -= n; // descomente para excluir palindromos de tamanho 1
total
}
fn main() {
let texto = "abaab";
let total = contar_palindromos(texto);
println!(
"Numero de substrings palindromicas em \"{}\": {}",
texto, total
);
// Palindromos: a, b, a, a, b, aba, aa, abaab? Nao.
// a(0), b(1), a(2), a(3), b(4), aba(0-2), aa(2-3), baab? Nao.
// Total: 7 (incluindo caracteres individuais)
}
3. Palindromos em Posicoes Especificas
/// Para cada posicao i, retorna o comprimento do maior palindromo
/// que TERMINA na posicao i. Util para problemas de DP com palindromos.
fn palindromos_terminando_em(texto: &str) -> Vec<usize> {
let n = texto.len();
let (_, _) = manacher(texto);
let bytes = texto.as_bytes();
let mut resultado = vec![1usize; n]; // no minimo o caractere sozinho
for i in 0..n {
// Expande palindromo impar terminando em i
for raio in 1..=i {
if i + 1 > raio * 2 {
let inicio = i - 2 * raio;
if bytes[inicio] == bytes[i] {
let meio_ok = resultado[i - 1] >= 2 * raio - 1
|| (inicio + 1 <= i - 1
&& texto[inicio + 1..i] == texto[inicio + 1..i]);
// Simplificacao: verifica diretamente
let sub = &bytes[inicio..=i];
let eh_palindromo = sub.iter().zip(sub.iter().rev()).all(|(a, b)| a == b);
if eh_palindromo {
resultado[i] = resultado[i].max(2 * raio + 1);
}
}
}
// Para eficiencia, podemos limitar as verificacoes
if raio > 10 {
break;
}
}
}
resultado
}
Aplicacoes Praticas
Verificador de Palindromos em Texto Natural
/// Encontra todas as palavras palindromicas em um texto.
fn encontrar_palindromos_palavras(texto: &str) -> Vec<String> {
let mut palindromos = Vec::new();
for palavra in texto.split_whitespace() {
// Remove pontuacao
let limpa: String = palavra
.chars()
.filter(|c| c.is_alphanumeric())
.collect();
let limpa_lower = limpa.to_lowercase();
if limpa_lower.len() >= 2 {
let bytes = limpa_lower.as_bytes();
let eh_palindromo = bytes.iter().zip(bytes.iter().rev()).all(|(a, b)| a == b);
if eh_palindromo {
palindromos.push(limpa);
}
}
}
palindromos
}
/// Particiona uma string no menor numero de substrings palindromicas.
fn particao_palindromica(texto: &str) -> Vec<String> {
let bytes = texto.as_bytes();
let n = bytes.len();
if n == 0 {
return Vec::new();
}
// DP: dp[i] = numero minimo de cortes para texto[0..i]
let mut dp = vec![usize::MAX; n + 1];
let mut corte = vec![0usize; n + 1]; // onde foi feito o melhor corte
dp[0] = 0;
// Pre-computa: eh_palindromo[i][j] para substrings curtas
let eh_pal = |i: usize, j: usize| -> bool {
let sub = &bytes[i..=j];
sub.iter().zip(sub.iter().rev()).all(|(a, b)| a == b)
};
for i in 1..=n {
for j in 0..i {
if dp[j] != usize::MAX && eh_pal(j, i - 1) {
if dp[j] + 1 < dp[i] {
dp[i] = dp[j] + 1;
corte[i] = j;
}
}
}
}
// Reconstroi a particao
let mut resultado = Vec::new();
let mut pos = n;
while pos > 0 {
resultado.push(texto[corte[pos]..pos].to_string());
pos = corte[pos];
}
resultado.reverse();
resultado
}
fn main() {
let texto = "abacdca";
let particao = particao_palindromica(texto);
println!("Particao palindromica de \"{}\": {:?}", texto, particao);
let texto2 = "O ovo da aba arara e legal";
let pals = encontrar_palindromos_palavras(texto2);
println!("Palavras palindromicas: {:?}", pals);
}
Comparacao com Outros Algoritmos
| Algoritmo | Tempo | Espaco | Encontra |
|---|---|---|---|
| Forca Bruta | O(n^3) | O(1) | Maior palindromo |
| Expandir do Centro | O(n^2) | O(1) | Maior palindromo |
| DP (tabela) | O(n^2) | O(n^2) | Todos palindromos |
| Manacher | O(n) | O(n) | Todos palindromos |
| Eertree/Palindromic Tree | O(n) | O(n) | Palindromos distintos |
| Suffix Array + LCP | O(n*log n) | O(n) | Maior palindromo |
O Manacher e a solucao otima para palindromos em uma string, com O(n) tanto em tempo quanto em espaco. Para palindromos em streams ou textos dinamicos, a Eertree (arvore palindromica) pode ser mais adequada.
Exercicios Praticos
Menor insercoes para palindromo: Dado um texto, encontre o menor numero de caracteres que devem ser inseridos para torna-lo um palindromo. Use Manacher para identificar o maior prefixo/sufixo palindromico.
Palindromos distintos: Conte quantos palindromos distintos existem como substrings de um texto. Combine Manacher com um HashSet.
Particao palindromica otima: Dado um texto, encontre o menor numero de cortes para particiona-lo em substrings que sao todas palindromos. Combine Manacher com programacao dinamica.
Palindromos em duas dimensoes: Dada uma matriz de caracteres, encontre o maior subretangulo cujas linhas E colunas sao todas palindromos. Aplique Manacher em cada dimensao.
Veja Tambem
- String na Biblioteca Padrao – manipulacao de strings em Rust
- Vec: Vetores Dinamicos – armazenamento do array P
- Slices em Rust – fatias para manipulacao eficiente de bytes
- Distancia de Levenshtein – outra abordagem para similaridade de strings
- Suffix Array – alternativa para encontrar palindromos via LCP