美文网首页
最长回文子字符串

最长回文子字符串

作者: 尼小摩 | 来源:发表于2018-07-11 14:27 被阅读13次

    1. 中心扩散法 Spread From Center

    复杂度

    时间 O(n^2) 空间 O(1)

    思路

    动态规划虽然优化了时间,但也浪费了空间。实际上我们并不需要一直存储所有子字符串的回文情况,我们需要知道的只是中心对称的较小一层是否是回文。所以如果我们从小到大连续以某点为个中心的所有子字符串进行计算,就能省略这个空间。 这种解法中,外层循环遍历的是子字符串的中心点,内层循环则是从中心扩散,一旦不是回文就不再计算其他以此为中心的较大的字符串。由于中心对称有两种情况,一是奇数个字母以某个字母对称,而是偶数个字母以两个字母中间为对称,所以我们要分别计算这两种对称情况。

    代码

    public class Solution {
        
        String longestPal = "";
        
        public String longestPalindrome(String s) {
            for (int i = 0; i < s.length(); i++) {
                // 计算奇数字符串
                helper(s, i, 0);
                // 计算偶数字符串
                helper(s, i, 1);
            }
            
            return longestPal;
        }
        
        private void helper(String s, int idx, int offset) {
            int left = idx;
            int right = idx + offset;
            while (left >= 0 && right < s.length() &&s.charAt(left) == s.charAt(right)) {
                left--;
                right++;
            }
            
            // 截取当前最长的回文子串
            String subPalind = s.substring(left + 1, right);
            // 判断是否比全局最长子串还长
            if (subPalind.length() > longestPal.length()) {
                longestPal = subPalind;
            }
        }
    }
    

    2. 动态规划 Dynamic Programming

    复杂度

    时间 O(n^2) 空间 O(n^2)

    思路

    根据回文的特性,一个大回文按比例缩小后的字符串也必定是回文,比如ABCCBA,那BCCB肯定也是回文。所以我们可以根据动态规划的两个特点:第一大问题拆解为小问题,第二重复利用之前的计算结果,来解答这道题。那如何划分小问题呢,我们可以先把所有长度最短为1的子字符串计算出来,根据起始位置从左向右,这些必定是回文。然后计算所有长度为2的子字符串,再根据起始位置从左向右。到长度为3的时候,我们就可以利用上次的计算结果:如果中心对称的短字符串不是回文,那长字符串也不是,如果短字符串是回文,那就要看长字符串两头是否一样。这样,一直到长度最大的子字符串,我们就把整个字符串集穷举完了,但是由于使用动态规划,使计算时间从O(N3)减少到O(n2)。

    注意

    外循环的变量控制的实际上不是字符串长度,而是字符串首到尾的增量
    二维数组的第一维是指子字符串起始位置,第二维是指终止位置,所存数据表示是否回文

    
    

    3. 马拉车算法 Manacher Algorithm

    复杂度

    时间 O(n) 空间 O(n)

    关于时间复杂度的证明:http://www.zhihu.com/question...

    思路

    Manacher算法是非常经典的计算连续下标回文的算法。它利用了回文的对称性,更具体的来说,是回文内回文的对称性,来解决这个问题。
    参见:https://www.jianshu.com/p/3cf0684972bd

    代码

    public class Solution {
        public String longestPalindrome(String s) {
            if(s.length()<=1){
                return s;
            }
            // 预处理字符串,避免奇偶问题
            String str = preProcess(s);
            // idx是当前能够向右延伸的最远的回文串中心点,随着迭代而更新
            // max是当前最长回文串在总字符串中所能延伸到的最右端的位置
            // maxIdx是当前已知的最长回文串中心点
            // maxSpan是当前已知的最长回文串向左或向右能延伸的长度
            int idx = 0, max = 0;
            int maxIdx = 0;
            int maxSpan = 0;
            int[] p = new int[str.length()];
            for(int curr = 1; curr < str.length(); curr++){
                // 找出当前下标相对于idx的对称点
                int symmetryOfCurr = 2 * idx - curr;
                // 如果当前已知延伸的最右端大于当前下标,我们可以用对称点的P值,否则记为1等待检查
                p[curr] = max > curr? Math.min(p[symmetryOfCurr], max - curr):1;
                // 检查并更新当前下标为中心的回文串最远延伸的长度
                while((curr+p[curr])<str.length() && str.charAt(curr+p[curr])==str.charAt(curr-p[curr])){
                    p[curr]++;
                }
                // 检查并更新当前已知能够延伸最远的回文串信息
                if(curr+p[curr]>max){
                    max = p[curr] + curr;
                    idx = curr;
                }
                // 检查并更新当前已知的最长回文串信息
                if(p[curr]>maxSpan){
                    maxSpan = p[curr];
                    maxIdx = curr;
                }
            }
            //去除占位符
            return s.substring((maxIdx-maxSpan)/2,(maxSpan+maxIdx)/2-1);
        }
        
        private String preProcess(String s){
            // 如ABC,变为$#A#B#C#
            StringBuilder sb = new StringBuilder();
            sb.append("$");
            for(int i = 0; i < s.length(); i++){
                sb.append("#");
                sb.append(s.charAt(i));
            }
            sb.append("#");
            return sb.toString();
        }
    }
    

    相关文章

      网友评论

          本文标题:最长回文子字符串

          本文链接:https://www.haomeiwen.com/subject/atshpftx.html