问题:
给定一个字符串 S,求出 S 的最长回文子串
思路:
1. 回文:一个字符串从前和从后读一致。S = "ABBA" 从前读:ABBA,从后读:ABBA
2. 最简单的做法:列出所有的子串,并判断是否是回文,从中找出最长的。
表格列出了所有的子串,第 i 行是以 S 的第 i 个字符开始的子串。S = "ABBA"
1 | A | AB | ABB | ABBA |
2 | B | BB | BBA | |
3 | B | BA | ||
4 | A |
从最后一行往上,可以看到他们的共同部分,用红色标出。共同的子问题~动态规划。
3. 动态规划,自底向上求解问题
1) 对子问题的结果进行存储和表示
len[i]:包含S[i] 的最长回文子串的长度
all[i]:S 的后半部分S[i, ..., n - 1] 中的最长回文子串长度
2) 把子问题的结果合并成它的上一层
子问题比它的上一层增加了一个字符。已经求得 S[i+1, ...] 子问题的解:从S[i+1]开始有len[i+1]长的回文子串 S[i+1]~S[n], all[i+1]
** 如果S[i]和这个子串的后一个字符一样,则从S[i]开始的回文子串可以包含它
比如:S="ABBA",已知 len[1] = 2,S[0]=S[3]="A",所以len[0] = len[1]+2
** 反之,从S[i]开始的回文子串不可以包含它,则需要对该子串从两头开始遍历
用两个变量分别从头 S[i] 和从尾S[n+1]开始比较,当发生不等的时候,就重新从头开始。
源码
1 class Solution { 2 public: 3 string longestPalindrome(string s) { 4 int ssize = s.length(); 5 int i, j, k, start; 6 int* len = new int[ssize]; 7 int* all = new int[ssize]; 8 //初始化,最长为1 9 len[ssize - 1] = all[ssize - 1] = 1;10 start = ssize - 1;11 12 for(i = ssize - 2; i >= 0; --i)13 {14 //计算len[i]15 if(i + len[i + 1] + 1 < ssize && s[i] == s[i + len[i + 1] + 1])//从S[i] 开始的回文子串包含S[i+1]开始的子串16 len[i] = len[i + 1] + 2;17 else//反之需要遍历18 {19 //j, k 表示从S[i] 开始可能的最长回文子串的区间20 j = i;21 k = i + len[i + 1];22 while(j < k)23 {24 if(s[j] == s[k])25 {26 ++j;27 --k;28 }29 else//两个字符不同时,j 需从头重新开始30 {31 j = i;32 --k;33 }34 }35 if(i == k && k == j)//36 len[i] = 1;37 else38 {39 len[i] = (k - i + 1) * 2;//子串长度为偶数40 if(j == k)//子串长度为奇数41 len[i]--;42 }43 }44 //计算all[i]45 if(len[i] > all[i + 1])46 {47 all[i] = len[i];48 start = i;49 }50 else51 {52 if(len[i] == all[i + 1])53 start = i;54 all[i] = all[i + 1];55 }56 }57 delete[] len;58 delete[] all;59 return s.substr(start, all[0]);60 }61 };