Palindrom je sekvence znaků, která zůstává stejná, ať už je čtena zleva doprava, nebo zprava doleva. Slovo jako „rotor“ je typickým příkladem palindromu. V mnoha situacích je potřeba v daném řetězci vyhledat ten nejdelší palindromický podřetězec. Taková operace má uplatnění třeba v bioinformatice při analýze a porovnávání sekvencí DNA.
Tento článek se zaměří na dva odlišné přístupy k řešení tohoto problému v jazyce Java: Metodu dynamického programování a Manacherův algoritmus.
Dynamické programování
Metoda dynamického programování pro hledání nejdelšího palindromického podřetězce se opírá o princip optimality, který zjednodušeně říká, že optimální řešení menších dílčích problémů je součástí optimálního řešení celého problému.
Kroky algoritmu:
- Vytvoříme dvourozměrnou tabulku
dp
. Prvekdp[i][j]
bude uchovávat délku nejdelšího palindromického podřetězce v daném řetězci v rozsahu znaků od indexui
do indexuj
. - Iniciačně nastavíme všechny hodnoty
dp[i][i]
na 1, protože každý jednotlivý znak je sám o sobě palindromem. - Pro výpočet hodnot
dp[i][j]
, kdei
je menší nežj
, provedeme následující:- Pokud se znaky na pozicích
s[i]
as[j]
shodují,dp[i][j]
nastavíme na hodnotudp[i+1][j-1] + 2
. - Pokud se znaky neshodují,
dp[i][j]
nastavíme na maximum z hodnotdp[i][j-1]
,dp[i+1][j]
adp[i+1][j-1]
.
- Pokud se znaky na pozicích
- Délka nejdelšího palindromického podřetězce se nakonec uloží v
dp[0][s.length() - 1]
.
Příklad implementace v Javě:
public class PalindromickyPodretezec {
public static void main(String[] args) {
String s = "kayak";
System.out.println(nejdelsiPalindromickyPodretezec(s)); // Výstup: kayak
}
public static String nejdelsiPalindromickyPodretezec(String s) {
int n = s.length();
int[][] dp = new int[n][n];
// Jednopísmenné palindromy
for (int i = 0; i < n; i++) {
dp[i][i] = 1;
}
// Palindromy délky 2
for (int i = 0; i < n - 1; i++) {
if (s.charAt(i) == s.charAt(i + 1)) {
dp[i][i + 1] = 2;
}
}
// Palindromy délky větší než 2
for (int l = 3; l <= n; l++) {
for (int i = 0; i <= n - l; i++) {
int j = i + l - 1;
if (s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1] > 0) {
dp[i][j] = l;
} else {
dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]);
}
}
}
// Získání nejdelšího palindromického podřetězce
StringBuilder sb = new StringBuilder();
int start = 0;
int end = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dp[i][j] > end - start) {
start = i;
end = j;
}
}
}
for (int i = start; i <= end; i++) {
sb.append(s.charAt(i));
}
return sb.toString();
}
}
Manacherův algoritmus
Manacherův algoritmus je považován za efektivnější metodu pro hledání nejdelšího palindromického podřetězce. Jeho klíčem je předzpracování řetězce, které spočívá v přidání speciálních znaků mezi jednotlivé znaky původního řetězce.
Kroky algoritmu:
- Upravíme původní řetězec tak, že mezi všechny znaky a na jeho začátek a konec vložíme znak ‚#‘.
- Pro každý znak
c
v upraveném řetězci určíme středi
a poloměrr
palindromu. - Poloměr palindromu rozšiřujeme tak dlouho, dokud platí podmínka, že
i - r
je větší nebo rovno 0,i + r
je menší než délka řetězce a současně se znaky na pozicíchi - r
ai + r
shodují. - Aktualizujeme poloměr palindromu, pokud je delší než aktuálně nejdelší nalezený poloměr.
- Vracíme nejdelší palindromický podřetězec, který má střed v původním řetězci.
Příklad implementace v Javě:
public class PalindromickyPodretezecManacher {
public static void main(String[] args) {
String s = "kayak";
System.out.println(nejdelsiPalindromickyPodretezecManacher(s)); // Výstup: kayak
}
public static String nejdelsiPalindromickyPodretezecManacher(String s) {
// Předzpracování
StringBuilder sb = new StringBuilder();
sb.append('#');
for (char c : s.toCharArray()) {
sb.append(c);
sb.append('#');
}
String t = sb.toString();
// Vytvoření pole P s poloměry palindromů
int n = t.length();
int[] p = new int[n];
int center = 0;
int right = 0;
int maxLen = 0;
int maxCenter = 0;
for (int i = 1; i < n; i++) {
int mirror = 2 * center - i;
if (i < right) {
p[i] = Math.min(right - i, p[mirror]);
}
// Rozšiřování palindromu
while (i - p[i] - 1 >= 0 && i + p[i] + 1 < n && t.charAt(i - p[i] - 1) == t.charAt(i + p[i] + 1)) {
p[i]++;
}
// Aktualizace maxima
if (p[i] > maxLen) {
maxLen = p[i];
maxCenter = i;
}
// Aktualizace středu a krajního bodu
if (i + p[i] > right) {
center = i;
right = i + p[i];
}
}
// Získání nejdelšího palindromického podřetězce
StringBuilder result = new StringBuilder();
for (int i = maxCenter - maxLen; i <= maxCenter + maxLen; i++) {
if (t.charAt(i) != '#') {
result.append(t.charAt(i));
}
}
return result.toString();
}
}
Závěr
Tento článek představil dva algoritmy pro vyhledávání nejdelšího palindromického podřetězce v řetězci v jazyce Java: Dynamické programování a Manacherův algoritmus. Obě metody jsou účinné, ale Manacherův algoritmus bývá zpravidla rychlejší. Volba vhodného algoritmu závisí na specifických požadavcích daného úkolu.