LeetCode————5.最长回文子串(常考)

题目(中等)

给定一个字符串 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是因为最长公共子串找到的索引是结束索引);在反向串”abac”中的结束索引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) 的定义如下:

1

基本示例如下:

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()]; //记录是否为回文串,不是就记为0,是的就记录回文串长度
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; //判断内部子串是不是回文串,不是为false,是的就记录回文串长度
}
}
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) //从left/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/

猜你喜欢

转载自www.cnblogs.com/liuzhongrong/p/12361629.html