圆形回文

时间:2016-07-29 12:27:32

标签: java algorithm palindrome

作为HackerRank challenge的一部分,我一直试图整天解决圆形回文问题。

传统的回文问题基本上是在较大的字符串中找到最长对称子串(回文)的长度。在此hackerRank challenge中,较大的字符串的长度限制为10 5 。它还要求我们找到每个旋转字符串的长度,从而增加了另一层复杂性。

已发布类似问题here,但我无法提取足够的信息以使我的代码足够快

我编写了以下Java代码,它是对旋转上下文中Manacher's algorithm的修改:

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));
    }
}  

它工作正常,但速度不够快。以下是“use_manacher = true”和HackerRank输入文件 input16.txt 的结果,这是一个复杂的测试用例,几乎有10个 5 字符。

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

“解决方案是正确的”意味着输出与HackerRank提供的输出相匹配。用“use_manacher = false”使它回退到一个简单的O(n 2 )算法,我们从每个可能的中心点开始,然后扩展到两边,直到达到字符串的长度我们有:

Total CPU time : 85.582152 sec
    Even palindromes took 20.451731 sec
    Odd palindromes took 20.389331 sec
    Length calculation took 44.741087 sec

最令我惊讶的是,使用Manacher算法的优化在循环环境中没有太大帮助(仅10-20%增益)。此外,找到圆形阵列中的回文(约35秒)比将它们映射到旋转弦中的长度(约45秒)花费的时间更少。

在HackerRank上有100多个成功提交的评分很高,我认为应该有一个解决方案可以在一个典型的CPU上在10秒内解决同样的问题:)

2 个答案:

答案 0 :(得分:1)

这仍然很慢......我所知道的是不要使用递归回文检查

static boolean isPali(String s) {
    if (s.length() == 0 || s.length() == 1)
        return true;
    if (s.charAt(0) == s.charAt(s.length() - 1))
        return isPali(s.substring(1, s.length() - 1));
    return false;
}

这是我的回答:

import java.io.*;
import java.util.*;

public class Solution {

   // I found this on stackoverflow it was about 3 times faster for a test I ran
   static boolean isPali(String str){
       StringBuilder sb = new StringBuilder(str);
       return str.equals(sb.reverse().toString());
   }

   static int subs(String s){
       int max=0;
       for(int j = 0 ; j < s.length(); j++ ) {
           for (int i = 1; i <= s.length() - j; i++) {
               String sub = s.substring(j, j+i);
               if(isPali(sub) && sub.length()>max){
                   max = sub.length();
               }
           }

       }
       return max;
   }

   static void rotation(int k,String s) {
       for (int i = 0; i < k; i++) System.out.println(subs(s.substring(i, k) +s.substring(0, i)));
   }


    public static void main(String args[]) {
        Scanner in = new Scanner(System.in);
        int k = in.nextInt();
        String s = in.next();
        rotation(k,s);
    }
}

答案 1 :(得分:1)

除此之外,您可以避免在子字符串大小小于2的情况下调用您的回文方法。 您使用的子字符串函数给出了所有可能的子字符串,包括单个字符子字符串。例如,如果您考虑字符串“abba”。对于此字符串,您的函数将为您提供以下10个子字符串:

<强>一个下,AB,ABB,ABBA,的 B'/ strong>下,BB,BBA,的 B'/ strong>下,BA,的

不应为所有这10个子字符串调用回文函数,而应仅为那些长度> = 2个字符的子字符串调用回文函数。 这样你就可以避免4次调用回文功能。 想象一下,如果你有一个10 ^ 5个字符的字符串。然后你将减少10 ^ 5个回调函数。我希望这对你有用。