题目(中等)
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad” 输出: “bab” 注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd” 输出: “bb”
解决方案
1.最大公共子串法
第一个比较容易的想法可能就是 把字符串S反转过来,记为S’,先使用动态规划方法 找到S与S’之间的最长公共子串,即所求的最长回文子串。
例如,S = “caba” , S’ = “abac”, S 以及 S’ 之间的最长公共子串为 “aba”,恰恰是答案。
让我们尝试一下这个例子:S = “abacdfgdcaba” , S’ = “abacdgfdcaba”, S 以及 S’之间的最长公共子串为 “abacd”,显然,这不是回文。
我们可以看到,当 S 的其他部分中存在非回文子串的反向副本时(即例子中的abacd和dcaba),最长公共子串法就会失败。为了纠正这一点,每当我们找到最长的公共子串的候选项时,都需要检查子串的索引是否与反向子串的原始索引相同 。如果相同,那么我们尝试更新目前为止找到的最长回文子串;如果不是,我们就跳过这个候选项并继续寻找下一个候选。
这里要解释一下上面加粗的那句,即判断条件,比如”caba”这个串,反转找到的最长公共子串是”aba”,它在”caba “的结束索引为3 (不是1是因为最长公共子串找到的索引是结束索引 );在反向串”aba c”中的结束索引 为2 ,我们要找的原始索引 就是在反向串中的反向索引 ,即”aba”的开始索引 (串长 4-1-结束索引2 =4-1-2=1 )加上该最大公共子串长度3再减1(即1 +3-1=3 )因为反向索引3 和原串的索引3 相等,所以认定这是个回文串,而不是反向副本;再比如”abacdfgdcaba”,找到的公共子串”abacd”的结束索引 为4 ,开始索引 =12-1-4=7,又因为最大公共子串长为5,所以反向串中的原始索引 就为7+5-1=11,这与原串的结束索引4不等,所以这是个反向副本,而不是回文数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
class { public String longestPalindrome (String s) { if (s.equals("" )) return "" ; String s1 = s; String s2 = new StringBuilder(s).reverse().toString(); String str="" ; int l1 = s1.length(); int l2 = s2.length(); int max_index1[] = new int [s.length()]; int max_index2[] = new int [s.length()]; int max_len = 0 ; int array[][] = new int [l1][l2]; for (int t=0 ;t<s.length();++t) { max_index1[t] = -1 ; max_index2[t] = -1 ; } for (int i=0 ;i<l1;++i) for (int j=0 ;j<l2;++j) { if (s1.charAt(i) == s2.charAt(j)) { if (i==0 || j==0 ) { array[i][j] = 1 ; } else array[i][j] = array[i-1 ][j-1 ]+1 ; if (array[i][j] > max_len) { int r_index2 = s.length()-j-1 ; if (i == r_index2+array[i][j]-1 ) { max_len = array[i][j]; max_index1[0 ] = i; max_index2[0 ] = j; for (int t=1 ;t<s.length();++t) { max_index1[t] = -1 ; max_index2[t] = -1 ; } } } } else array[i][j] = 0 ; } if (max_index1[0 ]>=0 ) str = (s1.substring(max_index1[0 ]-max_len+1 ,max_index1[0 ]+1 )); System.out.print(str); return str; } }
时间复杂度:因为是动态规划,所以是O(n2 )。
扫描二维码关注公众号,回复:
9397961 查看本文章
空间复杂度:因为用了空间换时间,所以是O(n2 )。其实还可以通过把Array二维数组转换为一维 ,因为每次循环都是固定i,内部循环j,而第3行的数据其实只用到之前第2行的数据,不需要第1行的,所以每次用一维数组重复利用,做数据更新就可以了。这样空间复杂度就缩小到了O(n)。
2.暴力法
很明显,暴力法将选出所有子字符串可能的开始和结束位置,并检验它是不是回文。但是暴力法面对比如”zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz”这样的超长回文串,就会因为时间开销太大而超时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
class { public String longestPalindrome (String s) { int max_len = 0 ; int max_index = -1 ; String str = "" ; for (int i=0 ;i<s.length();++i) for (int j=i;j<s.length();++j) { int start = i,end = j; while (s.charAt(start) == s.charAt(end)) { start++; end--; 大专栏 LeetCode————5.最长回文子串(常考) yword">if (end<0 || start>s.length()-1 ) break ; } if (start >= end && j-i+1 > max_len) { max_len = j-i+1 ; max_index = j; str = s.substring(i,j+1 ); } } System.out.println(str); return str; } }
时间复杂度:O(n3 ),因为要进行NN的循环,每次循环还要遍历根据初始位置start和结束位置end找出的子串判断是否是回文串,所以为N N * N。
空间复杂度:O(1)
3.动态规划法
为了改进暴力法,我们首先观察如何避免在验证回文时进行不必要的重复计算。考虑 “ababa” 这个示例。如果我们已经知道 “bab” 是回文,那么很明显,“ababa” 一定是回文,因为它的左首字母和右尾字母是相同的。
我们给出 P(i,j) 的定义如下:
基本示例如下:
P(i, i) = true
P(i, i+1) = (Si==Si+1)
这产生了一个直观的动态规划解法,我们首先初始化一字母和二字母的回文,然后找到所有三字母回文,并依此类推…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
class { public String longestPalindrome (String s) { int len[]=new int [s.length()]; int max_len=0 ; int max_index=0 ; String str="" ; for (int i=s.length()-1 ;i>=0 ;--i) for (int j=s.length()-1 ;j>=i;--j) { if (s.charAt(i) == s.charAt(j)) { if (i == j) len[j] = 1 ; else if (Math.abs(i-j)==1 ) { len[j] = 2 ; } else { len[j] = len[j-1 ]==0 ? 0 :len[j-1 ]+2 ; } } else len[j] = 0 ; if (len[j]>max_len) { max_len = len[j]; max_index = j; str = s.substring(i,j+1 ); } } System.out.println(str); return str; } }
时间复杂度:O(n2 )
空间复杂度:正常情况下是O(n2 ), 因为动态规划方法使用 O(n2 )的空间来存储表。同样的,这个空间复杂度可以优化到O(n) ,空间复杂度:O(n^2)O(n2), 该方法使用 O(n^2)O(n2) 的空间来存储表。
4.中心扩展法
事实上,只需使用恒定的空间,我们就可以在 O(n2 ) 的时间内解决这个问题。
我们观察到回文中心的两侧互为镜像。因此,回文可以从它的中心展开,并且只有 2n−1 个这样的中心。
你可能会问,为什么会是 2n−1 个,而不是 n 个中心?原因在于所含字母数为偶数的回文的中心可以处于两字母之间(例如 “abba” 的中心在两个 ‘b’ 之间)。所以找奇数长度的回文串时可以遍历n个中心(也就是n个字符均作为一次中心),找偶数长度回文串时可以遍历n-1个中心(也就是n个字符之间的空隙均作为一次中心),加起来就是n+n-1=2n-1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
class { public String longestPalindrome (String s) { if (s.equals("" )) return "" ; int max_len=0 ,length=0 ; int start=-1 ,end=-1 ; String str="" ; for (int i=0 ;i<s.length();++i) { int len1 = expandAroundCenter(s,i,i); int len2 = expandAroundCenter(s,i,i+1 ); length = (len1>len2)?len1:len2; if (length>max_len) { max_len = length; start = i-((length+1 )/2 -1 ); end = start+length-1 ; } } str = s.substring(start,end+1 ); System.out.println(str); return str; } public static int expandAroundCenter (String s,int left,int right) { int L=left,R=right; while (L>=0 && R<=s.length()-1 && s.charAt(L)==s.charAt(R)) { L--; R++; } return R-L-1 ; } }
时间复杂度:O(n2 ), 由于围绕中心来扩展回文会耗去 O(n) 的时间,所以总的复杂度为 O(n2 )。
空间复杂度:O(1)。
5.Manacher 马拉车算法
这个算法比较复杂,可以把时间复杂度和空闲复杂度都缩减到O(n),建议多查看一些资料,此处不做叙述。
参考资料:Longest Palindromic Substring Part II – LeetCode
https://articles.leetcode.com/longest-palindromic-substring-part-ii/