最长回文子串(优化思路较难理解)(5. 最长回文子串)

题目:

思路:

【1】先说说暴力破解,基于双循环取出所有的可以分割的字符串,然后判断是否是回文数,是的话记录长度是否比已知的还要长,是的话,则记录长度。暴力破解本身思想很简单,但是做的无用功也多,因为每个字符串都要处理一遍,然而很多字符串其实都是不需要的,那么减少要处理的字符串就是优化的重点。

【2】基于扩散思维的处理,首先在暴力破解里面我们写了对回文字符串的判断,那么回文字符串其实有两种情况,奇数串和偶数串,如:ABA和ABBA。那么便可以使用单循环替代双循环,遍历每一个字符串,查看它的扩散情况,最优的情况就是一开始就扩散不动,那么直接返回,跳过一大波的字符串,另一种就是最坏情况,扩散到边界。如aaabaaaa,当下标指向b的时候,扩散到极致便是aaabaaa,如aacbaaaa,当下标指向b的时候,一开始就扩散不动,那么就会直接跳过一大波暴力破解中分割出来的字符串。【总体而言就是筛选】

【3】Manacher(马拉车)算法的处理,本质上就是觉得中心扩散中处理的字符串还是太多了,能不能继续跳过一部分。又由于回文字符串有两种情况,不好处理,直接转为奇数串的形式。然后如aaabaaaa,会被置为#a#a#a#b#a#a#a#a#,如当遍历过b的时候其实就会将b置为当前最大的回文字符串中心下标7,且半径为8,所以得到的最右边的边界为14,所以当遍历到下标为8的a的时候先判断是否在容纳范围内,是的话先取出关于当前最大回文字符串中心下标的对称点的半径,如果它的半径范围也没有超出中心下标的边界,那么其实可以不用遍历了,因为必定是会被包含的。如果超出了,那么其实还是要走中心扩散的路子,自行找出最以该字符串进行中心扩散的情况,取出后要判断是否会对当前最大回文字符串进行替换。

代码展示:

基于暴力破解的方式:

//执行用时:304 ms, 在所有 Java 提交中击败了7.78%的用户
//内存消耗:41.5 MB, 在所有 Java 提交中击败了74.94%的用户
class Solution {
    public String longestPalindrome(String s) {
        if (s.length() < 2) {
            return s;
        }
        int start = 0;
        int maxLen = 0;
        for (int i = 0; i < s.length() - 1; i++) {
            for (int j = i; j < s.length(); j++) {
                // 截取所有子串,如果截取的子串小于等于之前遍历过的最大回文串,直接跳过。
                // 因为截取的子串即使是回文串也不可能是最大的,所以不需要判断
                if (j - i < maxLen) {
                    continue;
                }
                if (isPalindrome(s, i, j)) {
                    if (maxLen < j - i + 1) {
                        start = i;
                        maxLen = j - i + 1;
                    }
                }
            }
        }
        return s.substring(start, start + maxLen);
    }

    //判断是否是回文串
    private static boolean isPalindrome(String s, int start, int end) {
        while (start < end) {
            if (s.charAt(start++) != s.charAt(end--)) {
                return false;
            }
        }
        return true;
    }
}

基于暴力破解的优化【中心扩散】:

//执行用时:20 ms, 在所有 Java 提交中击败了72.27%的用户
//内存消耗:41.1 MB, 在所有 Java 提交中击败了92.68%的用户
class Solution {
    public String longestPalindrome(String s) {
        String res = "";

        for (int i = 0; i < s.length(); i++) {
            String s1 = isPalindrome(s, i, i);
            String s2 = isPalindrome(s, i, i + 1);

            res = res.length() > s1.length() ? res : s1;
            res = res.length() > s2.length() ? res : s2;
        }

        return res;
    }

    //直接向两边扩散取出最长回文字符串
    public String isPalindrome(String s, int l, int r) {
        while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
            l--;
            r++;
        }
        return s.substring(l + 1, r);
    }
}

 最优解法【Manacher(马拉车)算法】:

//执行用时:3 ms, 在所有 Java 提交中击败了98.18%的用户
//内存消耗:41.3 MB, 在所有 Java 提交中击败了80.14%的用户
class Solution {
    public String longestPalindrome(String s) {
        int charLen = s.length();//源字符串的长度
        int length = charLen * 2 + 1;//添加特殊字符之后的长度
        char[] chars = s.toCharArray();//源字符串的字符数组
        char[] res = new char[length];//添加特殊字符的字符数组
        int index = 0;
        //添加特殊字符
        for (int i = 0; i < res.length; i++) {
            res[i] = (i % 2) == 0 ? '#' : chars[index++];
        }

        //新建p数组 ,p[i]表示以res[i]为中心的回文串半径
        int[] p = new int[length];
        //maxRight(某个回文串延伸到的最右边下标)
        //maxCenter(maxRight所属回文串中心下标),
        //resCenter(记录遍历过的最大回文串中心下标)
        //resLen(记录遍历过的最大回文半径)
        int maxRight = 0, maxCenter = 0, resCenter = 0, resLen = 0;
        //遍历字符数组res
        for (int i = 0; i < length; i++) {
            if (i < maxRight) {
                //情况一,i没有超出范围[left,maxRight]
                //2 * maxCenter - i其实就是j的位置,实际上是判断p[j]<maxRight - i
                if (p[2 * maxCenter - i] < maxRight - i) {
                    //j的回文半径没有超出范围[left,maxRight],直接让p[i]=p[j]即可
                    p[i] = p[2 * maxCenter - i];
                } else {
                    //情况二,j的回文半径已经超出了范围[left,maxRight],我们可以确定p[i]的最小值
                    //是maxRight - i,至于到底有多大,后面还需要在计算
                    p[i] = maxRight - i;
                    while (i - p[i] >= 0 && i + p[i] < length && res[i - p[i]] == res[i + p[i]])
                        p[i]++;
                }
            } else {
                //情况三,i超出了范围[left,maxRight],就没法利用之前的已知数据,而是要一个个判断了
                p[i] = 1;
                //首先(i - p[i])和(i + p[i])为双指针,所以应在数组下标范围内,不得出现数组越界问题
                //其次,比较两者相同则双指针各移动一位
                while (i - p[i] >= 0 && i + p[i] < length && res[i - p[i]] == res[i + p[i]])
                    p[i]++;
            }
            //匹配完之后,如果右边界i + p[i]超过maxRight,那么就更新maxRight和maxCenter
            if (i + p[i] > maxRight) {
                maxRight = i + p[i];
                maxCenter = i;
            }
            //记录最长回文串的半径和中心位置
            if (p[i] > resLen) {
                resLen = p[i];
                resCenter = i;
            }
        }
        //计算最长回文串的长度和开始的位置
        resLen = resLen - 1;
        int start = (resCenter - resLen) >> 1;
        //截取最长回文子串
        return s.substring(start, start + resLen);
    }

}

 

posted @ 2023-01-13 11:22  忧愁的chafry  阅读(43)  评论(0编辑  收藏  举报