1. Question
求最长回文子串
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.
2. Solution
2.1 O(n3)
对每个字符,考察该字符(作为首字符)与其余字符(作为尾字符)组成的子串是否是回文串,找到最长的。判断是否是回文串耗时O(n),因此时间复杂度O(n3)
2.2 O(n2)
2.2.1 中心向外扩充法
对每个字符,将其作为回文串中心字符(可通过插入辅助字符使得仅考虑奇数回文串情况,eg. abcd--->#a#b#c#d#),考虑其最长回文串长度。此时,为每个中心字符找回文串用时O(n),因此时间复杂度O(n2)
1 public class Solution { 2 private int maxStart; 3 private int maxEnd; 4 5 private void expand( String s, int from, int end ){ 6 for( ; from>=0 && end<s.length() && (s.charAt(from) == s.charAt(end) ); from--,end++ ); 7 end--; 8 from++; 9 if( end-from > maxEnd-maxStart ){ 10 maxStart = from; 11 maxEnd = end; 12 } 13 } 14 15 //expand from a middle point, O(n2) time 16 public String longestPalindrome( String s ){ 17 maxStart = 0; 18 maxEnd = 0; 19 for( int i=0; i<s.length()-1; i++ ){ 20 expand( s,i,i ); //the result length is odd 21 expand( s,i,i+1 ); //the result length is even 22 } 23 return s.substring(maxStart, maxEnd+1); 24 } 25 }
2.2.2 按长度依次扩充
人在找回文串时,一般都是先看到较短的回文串,然后再慢慢往外扩(类似上述中心向外扩充),即先找长度为2的回文串,再找长度为3的,为4的...长度长的可以复用长度短的信息。
由此,定义二维数组a[][]存储回文串信息,a[i][j]表示字符i到j是否是回文串,则有:
a[i][i] = true;
a[i][i+1] = ( chars[i] == chars[i+1] );
a[i][j] = ( chars[i] == chars[j] ) && a[i+1][j-1];
1 public class Solution { 2 //DP, O(n2) time 3 public String longestPalindrome( String s ){ 4 if( s.length()==1 ) return s; 5 if( s.length() == 2 ){ 6 if( s.charAt(0) == s.charAt(1) ) 7 return s; 8 return s.substring(0, 1); 9 } 10 //s.length() >2 11 boolean[][] p = new boolean[s.length()][s.length()]; 12 int i; 13 for( i=0; i<s.length()-1; i++ ){ 14 p[i][i] = true; 15 p[i][i+1] = ( s.charAt(i) == s.charAt(i+1) ); 16 } 17 p[i][i] = true; 18 for( i=2; i<s.length(); i++ ) 19 for( int j=0, k=i; k<s.length(); j++, k++ ) 20 p[j][k] = ( s.charAt(j) == s.charAt(k) ) && p[j+1][k-1]; 21 22 int maxStart = 0; 23 int maxEnd = 0; 24 for( i=0; i<s.length(); i++ ) 25 for( int j=s.length()-1; j>=i; j-- ){ 26 if( p[i][j] ){ 27 if( j-i > maxEnd-maxStart ){ 28 maxStart = i; 29 maxEnd = j; 30 } 31 break; 32 } 33 } 34 return s.substring(maxStart, maxEnd+1); 35 } 36 }
2.3 O(n)
考虑中心向外扩充,它遍历每个字符为其找回文串。而事实上,由于回文串的对称性,中心点左侧的回文性和右侧字符的部分回文性是一致的,这部分数据可以重用,而不用重复计算。
改进上述算法,重新定义下列变量:
- r[]:存放以每个点为中心点的最长回文串的半径(以便复用该信息)
- max:存放目前回文串走到最远的中心字符索引,即以max为中心的回文串,其回文串最后一个字符是目前走的最远的。下面简称max为最远覆盖点,回文串范围为覆盖范围
- i:定义当前访问的字符索引,要为字符i计算其回文串长度
- 如果i在max的覆盖范围,考察i的对称点的覆盖范围
- 如果对称点覆盖边界在max覆盖边界之内,则无需对i再扩展,r[i] = r[i的对称点]
- 如果对称点覆盖边界与max某覆盖边界重合,则对于i,最远覆盖边界之外的点是下一个可能在i的回文串范围的字符,应当从最远覆盖边界开始继续向外扩充,计算r[i]
- 如果不在,以i为中心点向外扩充(此时没有可利用的信息)
- 如果i在max的覆盖范围,考察i的对称点的覆盖范围
利用渐进计算分析可知,该算法时间复杂度O(n)
1 public class Solution { 2 3 //adding extra character to make all substring as an odd number string. such as: abcd--->#a#b#c#d# 4 private String preProcess( String s ){ 5 StringBuilder res = new StringBuilder(); 6 for( int i=0; i<s.length(); i++ ){ 7 res.append('#'); 8 res.append( s.charAt(i) ); 9 } 10 res.append( '#' ); 11 return res.toString(); 12 } 13 14 //expand a palindrome, return the expanding length 15 private int expand( String s, int start, int end ){ 16 int newS = start; 17 int newE = end; 18 for( ; newS >= 0 && newE < s.length() && s.charAt(newS) == s.charAt(newE); newS--, newE++ ); 19 return start - newS; 20 } 21 22 //O(n) time, using symmetry feature, calculate the longest palindromic centered by each point 23 public String longestPalindrome( String s ){ 24 String newS = preProcess(s); 25 26 int[] r = new int[newS.length()]; 27 r[0] = 0; 28 int max = 0; 29 30 for( int i=1; i<newS.length(); i++ ){ 31 int j = r[max] + max; //present farthest palindrome range, inclusive 32 if( j >= newS.length() ) 33 break; 34 //if i is in the range, it's possible to fast calculate the palindromic length for point i 35 if( i<=j ){ 36 int symI = r[2*max-i]; 37 int remainRange = j - i; 38 if( symI < remainRange ) 39 r[i] = symI; 40 else 41 r[i] = expand( newS, i-remainRange-1, j+1 ) + remainRange; 42 } 43 //if i is not int the range, r[i] = expand(i) 44 else 45 r[i] = expand( newS, i-1, i+1 ); 46 47 if( r[i] + i > j ) max = i; 48 } 49 max = 0; 50 for( int i=0; i<r.length; i++ ) 51 if( r[i] > r[max] ) max = i; 52 return s.substring( (max-r[max]+1)/2, (max+r[max])/2 ); 53 } 54 }