LeetCodeOJ.Longest Palindromic Substring

来源:互联网 发布:centos 6.5网络配置ip 编辑:程序博客网 时间:2024/06/08 18:58

试题请参见: https://leetcode.com/problems/longest-palindromic-substring/

题目概述

Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.

解题思路

Manacher 算法

对字符串进行预处理

首先, 我们需要对字符串S进行预处理, 并将结果放在字符串T中. 我们会在字符串S的每个字符之间插入特殊字符(包括字符串的首尾). 预处理后的结果如下:

S: abba     T: ^#a#b#b#a#$S: a        T: ^#a#$

我们可以使用如下代码进行字符串的预处理:

private String getPreprocessedString(String s) {    StringBuilder sb = new StringBuilder("^#");    for ( int i = 0; i < s.length(); ++ i ) {        sb.append(s.charAt(i) + "#");    }    sb.append("$");    return sb.toString();}

处理字符串T

当预处理完成得到字符串T后, 接下来需要对T进行处理. 我们需要声明一个与T等长度的辅助数组P.
数组P的第i个元素对应了第i位可以向两侧扩展的最大回文串的长度.

举个栗子:

i: 0 1 2 3 4 5 6 7 8 9 10T: ^ # a # b # a # c # $

那么T中最长的回文字符子串是什么? 显然是” # a # b # a #”, 一个长度为7的字符串. 我们也注意到, 字符’b’, i = 4, 是这个回文串的中心.

现在的问题是, 从回文串的右侧至左侧一共有多少字符呢? 或者说回文串的长度是多少呢? 答案很显然是3.

i:          4            T: ^ |# a # b # a #| c # $P: 0 |- - - 3 - - -| 1 0 0

最终的目标是计算得到P, 这项工作似乎简单了很多. 结果如下:

i: 0 1 2 3 4 5 6 7 8 9 10T: ^ # a # b # a # c # $P: 0 0 1 0 3 0 1 0 1 0 0

为了方便, 我们引入另一个辅助变量maxPalCenterID用来在计算数组P时记录数组P中最大的下标. 当P数组计算完成后, 很容易从原字符串S中提取最长的回文串. 因为在预处理过程中向T中插入了特殊字符, 因此回文串的起始下标应该从如下公式计算得到:

palStartID = (maxPalCenterID - 1 - P[maxPalCenterID]) / 2;

于是我们可以得到如下代码. 这段代码仍然不是O(n)的时间复杂度. 我们会在下文做进一步优化. 但此时已经可以通过LeetCode上的所有数据.

public String longestPalindrome(String s) {    String t = getPreprocessedString(s);    int n = t.length(), maxPalCenterID = 0;    int[] p = new int[n];    for ( int i = 1; i < n - 1; ++ i ) {        while ( t.charAt(i + 1 + p[i]) == t.charAt(i - 1 - p[i]) ) {            ++ p[i];        }        if ( p[i] > p[maxPalCenterID] ) {            maxPalCenterID = i;        }    }    int maxPalSize = p[maxPalCenterID];    int palStartID = (maxPalCenterID - 1 - maxPalSize) / 2;    return s.substring(palStartID, palStartID + maxPalSize);}

优化至O(n)的复杂度

优化的策略是基于回文串的对称性的. 考虑下面的栗子. 其中C表示回文串的中心, L和R分别表示回文串的左右边界.

i:    0 1 2 3 4 5 6 7 8 9 10  12  14  16vars:   L     C     RT:    ^ # a # b # a # c # a # b # b  $P:      0 0 1 0 3

此时我们计算数组P直至R, 也就是回文串的右侧边界. 注意到对称性了吗? 好像在C处有一面镜子一样, 在数组P中的L和R之间, 数值是完全对称的.

vars:   L     C     RT:    ^ # a # b # a # c # a # b # b # $P:    0 0 1 0 3 0 1 0        + + + | + + +

现在你可能会想, 通过计算从左侧到中间的数值, 并且根据对称性得到右侧的数值, 我们可以节省很多的计算量. 我们来试试看, 在C = 8时我们仍然通过这个”想法”计算得到P数组的数值. 然而你会发现这个想法并不正确:

i:    0 1 2 3 4 5 6 7 8 9 10  12  14  16vars:       L         C         RT:    ^ # a # b # a # c # a # b # b # $P:    0 0 1 0 3 0 1 0 5 0 1 0 3 0             + + + + + | + + + + +

然而你会发现在第12位的b并没有正确的数值. 正确的结果如下:

i:    0 1 2 3 4 5 6 7 8 9 10  12  14  16vars:       L         C         RT:    ^ # a # b # a # c # a # b # b # $P:    0 0 1 0 3 0 1 0 5 0 1 0 1 2 1 0 0            + + + + + | + + + + +

如果你仔细观察测试用例, 你会发现当P[i’]<=R-i 时, P[i]始终等于R-i 或 P[i’]中较小的数值; 否则, 我们只能得到一个信息: P[i’]>=P[i]. 在这种情况下, 我们需要尝试向上一节所做的去扩展回文串以找到P[i]的数值.

最后我们需要讨论一个问题, C的值何时被更新? 根据手中的测试用例, 不难得出, 当一个回文串向右侧扩展超过边界 R 时(即i + p[i] > r), 我们另 C 的值为 i, 并且令 R 的值为i + P[i].

这就是Manacher算法, 一个以 O(n)时间复杂度找出最长回文子串的工具.

源代码

public class Solution {    public String longestPalindrome(String s) {        String t = getPreprocessedString(s);        int n = t.length(), maxPalCenterID = 0;        int c = 0, r = 0;        int[] p = new int[n];        for ( int i = 1; i < n - 1; ++ i ) {            int ii = 2 * c - i;            p[i] = ( r > i ) ? Math.min(r - i, p[ii]) : 0;            while ( t.charAt(i + 1 + p[i]) == t.charAt(i - 1 - p[i]) ) {                ++ p[i];            }            if ( p[i] > p[maxPalCenterID] ) {                maxPalCenterID = i;            }            if ( i + p[i] > r ) {                c = i;                r = i + p[i];            }        }        int maxPalSize = p[maxPalCenterID];        int palStartID = (maxPalCenterID - 1 - maxPalSize) / 2;        return s.substring(palStartID, palStartID + maxPalSize);    }    private String getPreprocessedString(String s) {        StringBuilder sb = new StringBuilder("^#");        for ( int i = 0; i < s.length(); ++ i ) {            sb.append(s.charAt(i) + "#");        }        sb.append("$");        return sb.toString();    }    public static void main(String[] args) {        Solution s = new Solution();        System.out.println(s.longestPalindrome("ccabcbac"));    }}

参考资料

  • http://difusal.blogspot.com/2014/08/manachers-algorithm-longest-palindromic.html
1 0
原创粉丝点击