Ich habe versucht, das Problem der kreisförmigen Palindrom den ganzen Tag, als Teil einer HackerRank challenge zu lösen.Circular Palindrome
Das traditionelle Palindrom-Problem besteht im Wesentlichen darin, die Länge der längsten symmetrischen Teilstrings (Palindrome) innerhalb eines größeren Strings zu finden. In diesem hackerRank challenge hat die größere Zeichenfolge eine Längenbeschränkung von 10 . Es fügt auch eine weitere Ebene der Komplexität hinzu, indem es nach den Längen für jede Rotationskette fragt.
Eine ähnliche Frage wurde geschrieben here, aber ich konnte nicht genug Informationen extrahieren, um meinen Code schnell genug zu arbeiten.
Ich habe den folgenden Java-Code geschrieben, das eine Modifikation der Manacher's algorithm in einem gedrehten Kontext ist:
package cards.myb.algorithms;
import java.io.*;
import java.util.*;
import java.text.*;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.math.*;
import java.util.regex.*;
public class CircularPalindrome {
public static void main(String[] args) {
/* Enter your code here. Read input from STDIN. Print output to STDOUT. Your class should be named Solution. */
boolean use_manacher = true;
int N = 0;
String S = "";
String inputFile = "D:\\Home\\Java\\AlgorithmPractice\\input16.txt";
String solutionFile = "D:\\Home\\Java\\AlgorithmPractice\\output16.txt";
System.out.println("Reading file " + inputFile);
File file = new File(inputFile);
try {
FileInputStream fis = new FileInputStream(file);
BufferedReader in = new BufferedReader(new InputStreamReader(fis));
N = Integer.valueOf(in.readLine());
S = in.readLine();
in.close();
} catch(IOException e) {
e.printStackTrace();
return;
}
// Convert string to char for efficiency
char[] sArr = S.toCharArray();
// Start timer
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long tStartNS = threadMXBean.getCurrentThreadCpuTime();
System.out.println("Starting clock");
// Allocate space for Sk, k=0...N-1
int[] lengths = new int[N];
int[] plenEvenArr = new int[N];
int[] plenOddArr = new int[N];
// ********************************************
// Part 1 : Even palindromes
// ********************************************
int best_right = N-1, best_left = 0, best_plen = 0;
for(int i=0; i<N; i++) {
// i points to the position at the palindrome center (between two elements)
boolean do_loop = true;
int left, right, plen;
// Mirror image optimization
// Manacher's algorithm
if(!use_manacher || i > best_right) {
left = i;
right = (i-1+N)%N;
plen = 0;
//System.out.println("plen=" + plen + ", right=" + right + ", left=" + left);
} else {
int i2 = (best_left + best_right - i + 1 + N) % N;
//System.out.println("i=" + i + ", best_left = " + best_left + ", best_right=" + best_right + ", i2=" + i2);
if(i2 >= i) {
left = i;
right = (i-1+N)%N;
plen = 0;
//System.out.println("plen=" + plen + ", right=" + right + ", left=" + left);
} else if(plenEvenArr[i2] < ((best_right - i + 1 + N) % N) * 2) {
plen = plenEvenArr[i2];
do_loop = false;
left = right = 0; // Avoid warnings
//System.out.println("use mirror image plenArr[i2]=" + plenArr[i2]);
} else {
plen = ((best_right - i + 1 + N) % N) * 2;
right = best_right;
left = (best_right - plen + 1 + N) % N;
//System.out.println("start from plen=" + plen + ", right=" + right + ", left=" + left);
}
}
// Find maximum even palindrome with center at i
for(; do_loop && plen < N-1; plen += 2) {
char expandleft = sArr[(left - 1 + N) % N];
char expandright = sArr[(right + 1) % N];
if(expandleft == expandright) {
left = (left - 1 + N) % N;
right = (right + 1) % N;
} else
break;
}
plenEvenArr[i] = plen;
// Keep track of best
if(plen > best_plen) {
best_right = right;
best_left = left;
best_plen = plen;
}
}
long tEvenNS = threadMXBean.getCurrentThreadCpuTime();
// ********************************************
// Part 2 : Odd palindromes
// ********************************************
best_right = 0; best_left = 0; best_plen = 1;
for(int i=0; i<N; i++) {
// i points to the middle element of the palindrome
boolean do_loop = true;
int left, right, plen;
// Mirror image optimization
// Manacher's algorithm
if(!use_manacher || i > best_right) {
left = right = i;
plen = 1;
// System.out.println("plen=" + plen + ", right=" + right + ", left=" + left);
} else {
int dist_to_best_right = (best_right - i + N) % N;
int i2 = (best_left + dist_to_best_right) % N;
int plen_best = dist_to_best_right * 2 + 1;
// System.out.println("i=" + i + ", best_left = " + best_left + ", dist_to_best_right=" + dist_to_best_right + ", best_right=" + best_right + ", i2=" + i2);
if(i2 >= i) {
left = right = i;
plen = 1;
// System.out.println("plen=" + plen + ", right=" + right + ", left=" + left);
} else if(plenOddArr[i2] < plen_best) {
plen = plenOddArr[i2];
do_loop = false;
left = right = 0; // Avoid warnings
// System.out.println("use mirror image plenArr[i2]=" + plenArr[i2]);
} else {
plen = plen_best;
right = best_right;
left = (i - dist_to_best_right + N) % N;
// System.out.println("start from plen=" + plen + ", right=" + right + ", left=" + left);
}
}
// Find maximum odd palindrome with center character at i
for(; plen < N-1 && do_loop; plen += 2) {
char expandleft = sArr[(left - 1 + N) % N];
char expandright = sArr[(right + 1) % N];
if(expandleft == expandright) {
left = (left - 1 + N) % N;
right = (right + 1) % N;
} else
break;
}
plenOddArr[i] = plen;
// Keep track of best
if(plen > best_plen) {
best_right = right;
best_left = left;
best_plen = plen;
}
}
long tOddNS = threadMXBean.getCurrentThreadCpuTime();
// ********************************************
// Part 3 : Find maximum palindrome for Sk
// ********************************************
for(int i=0; i<N; i++)
lengths[i] = 1;
for(int i=0; i<N; i++) {
int plenEven = plenEvenArr[i];
if(plenEven > 1) {
for(int k=0; k<N; k++) {
// Calculate length of the palindrome in Sk
int spaceLeft = (i >= k) ? (i - k) : (N + i - k);
int spaceRight = (i > k) ? (N + k - i) : (k - i);
// Corner case: i=k and plen=N
int len;
if(i==k && plenEven == N)
len = plenEven;
else
len = Math.min(plenEven, Math.min(spaceLeft*2, spaceRight*2));
// Update array
lengths[k] = Math.max(lengths[k], len);
}
}
}
for(int i=0; i<N; i++) {
int plenOdd = plenOddArr[i];
if(plenOdd > 1) {
for(int k=0; k<N; k++) {
// Calculate length of the palindrome in Sk
int spaceLeft = (i >= k) ? (i - k) : (N + i - k);
int spaceRight = (i >= k) ? (N + k - i - 1) : (k - i - 1);
int len = Math.min(plenOdd, Math.min(spaceLeft*2+1, spaceRight*2+1));
// Update array
lengths[k] = Math.max(lengths[k], len);
}
}
}
// End timer
long tEndNS = threadMXBean.getCurrentThreadCpuTime();
System.out.println("Clock stopped");
// Print result
for(int i=0; i<N; i++) {
System.out.println(lengths[i]);
}
// Read solution from file
int[] solution = new int[N];
System.out.println("Reading solution file " + solutionFile);
File solfile = new File(solutionFile);
try {
BufferedReader solin = new BufferedReader(new InputStreamReader(new FileInputStream(solfile)));
for(int i=0; i<N; i++) {
solution[i] = Integer.valueOf(solin.readLine());
}
solin.close();
} catch(IOException e) {
e.printStackTrace();
return;
}
// Check solution correctness
boolean correct = true;
for(int i=0; i<N; i++) {
if(lengths[i] != solution[i]) {
System.out.println(String.format("Mismatch to solution: lengths[%d] = %d (should be %d)", i, lengths[i], solution[i]));
correct = false;
}
}
if(correct)
System.out.println("Solution is correct");
// Calculate/print total elapsed time
System.out.println(String.format("Total CPU time : %.6f sec", (float)(tEndNS - tStartNS)/1.0e9));
System.out.println(String.format(" Even palindromes took %.6f sec", (float)(tEvenNS - tStartNS)/1.0e9));
System.out.println(String.format(" Odd palindromes took %.6f sec", (float)(tOddNS - tEvenNS)/1.0e9));
System.out.println(String.format(" Length calculation took %.6f sec", (float)(tEndNS - tOddNS)/1.0e9));
}
}
Es funktioniert OK, aber nicht schnell genug. Hier sind die Ergebnisse mit "use_manager = true" und HackerRank Eingabedatei input16.txt, die ein komplexer Testfall mit fast 10 Zeichen ist.
Solution is correct
Total CPU time : 79.841313 sec
Even palindromes took 18.220917 sec
Odd palindromes took 16.738907 sec
Length calculation took 44.881486 sec
"Lösung ist korrekt" bedeutet, dass die Ausgabe mit der von HackerRank bereitgestellten übereinstimmt. Mit "use_manacher = false", so dass es auf einen einfachen O (n) Algorithmus zurückgeht, wo wir von jedem möglichen Mittelpunkt aus beginnen und nach beiden Seiten expandieren, bis wir die Länge der Zeichenfolge erreichen, die wir haben:
Was mich am meisten überrascht ist, dass die Optimierung mit Manachers Algorithmus nicht zu viel in einem kreisförmigen Kontext half (nur 10-20% Gewinn). Auch das Finden der Palindrome in einem kreisförmigen Array (~ 35 Sekunden) dauerte weniger Zeit als das Mapping auf Längen in gedrehten Strings (~ 45 Sekunden).
Es gibt mehr als 100 erfolgreiche Einreichungen mit perfekter Punktzahl auf HackerRank, was meiner Meinung nach bedeutet, soll es eine Lösung sein, die das gleiche Problem innerhalb von 10 Sekunden auf einer typische CPU löst :)
Große Frage! Der Arbeitscode sollte wahrscheinlich auf unsere Schwesterseite http://codereview.stackexchange.com/ migriert werden. – rajah9
Kannst du die% Operationen in den inneren Schleifen, die zeitaufwendig sind, nicht loswerden? –
Sie können definitiv keine doppelten For-Schleifen machen, wenn Sie 'N'-Male (insgesamt' N^2') in Ihrem Teil durchlaufen. 3. Ich schätze, die richtige Lösung wird einige Memo-Operationen beinhalten. – justhalf