微软面试题: LeetCode 5. 最长回文子串 出现次数:1
方法一 中心扩散法
解析: 回文串一定是 中心对称的,回文串的对称中心可能是 1 个字符,也可能是 2个字符,遍历 s 中的 字符
分别以 s[i] 和s[i] 、s[i+1] 为中心像两边扩散,记录 最长 回文串的 起始位置 和长度。
时间 O(n ^ 2) 空间 O(1)
代码:
1 //方法一: 中心扩散法 时间 O(n ^ 2) 空间:O(1) 2 string CentralDiffusion(string s) 3 { 4 int sub_len = 1; 5 int start = 0; 6 7 for(int i = 0; i < s.size();++i) 8 { 9 int l,r,sub_len_tmp; 10 //考虑以(s[i],s[i+1])为回文串中心的情况 11 if(i + 1 < s.size() && s[i] == s[i+1]) 12 { 13 l = i; 14 r = i + 1; 15 sub_len_tmp = 0; 16 while(l >= 0 && r < s.size() && s[l] == s[r]) 17 { 18 sub_len_tmp += 2; 19 --l; 20 ++r; 21 } 22 if(sub_len_tmp > sub_len) 23 { 24 start = l + 1; 25 sub_len = sub_len_tmp; 26 } 27 } 28 //考虑以s[i]为奇数回文串中心的情况 29 l = i - 1; 30 r = i + 1; 31 sub_len_tmp = 1; 32 while(l >= 0 && r < s.size() && s[l] == s[r]) 33 { 34 sub_len_tmp += 2; 35 --l; 36 ++r; 37 } 38 if(sub_len_tmp > sub_len) 39 { 40 start = l + 1; 41 sub_len = sub_len_tmp; 42 } 43 } 44 return s.substr(start,sub_len); 45 }
方法二 动态规划
分析:
1 个字符 s[i] 的子串一定是回文串,
2个字符 s[ i : i + 1] 当 s[i] == s[i+1] 时 是回文串,否则不是,
3 个字符的子串 s[i : i + 2] : 当 s[i] == s[i+2] 时是回文子串,此时 s[i : i + 2] 内层的 s[i+1:i+1] 只有一个字符,一定是回文的。
4 个字符 s[i : i+3] : 当 s[i] == s[i+3] 时是回文子串 而且 其内层 s[i+1:i+2] 也是回文串时, s[i : i+3] 是回文串。
........
1. 定义状态 dp[ i ][ j ] , 等于 1 表示 子串 s[ i : j ] 是回文串,等于 0 不是回文串。 0<= j < n, 0 <= i <=j;
2. 状态转移方程 dp[ i ][ j ] = dp[ i + 1][ j - 1] == 1 && s[i] == s[j] ?1:0;
3. 设置初始状态 dp[ i ][ i ] = 1, dp[ i ][ i + 1] = s[ i ] == s [ i + 1 ] ? 1 : 0;
1 //方法二: 动态规划 时间 O(n ^ 2) 空间:O(n) 2 // dp 的方法虽然时间复杂度也是 O(n ^ 2),但是和中心扩散法相比,避免了很多重复计算,有很好的的性能提升 3 string dp(string s) 4 { 5 const int s_len = s.size(); 6 if(s_len <= 1) 7 { 8 return s; 9 } 10 int start = 0; 11 int sub_len = 1; 12 //定义状态 dp[i][j] , 等于 1 表示s[i:j]是回文子串,等0不是 13 int dp[s_len][s_len] ; 14 memset( dp, 0, sizeof(dp)); 15 for(int i = 0; i < s_len; ++i)// dp base case 16 { 17 dp[i][i] = 1; 18 if(i+1 < s_len && s[i] == s[i+1]) 19 { 20 dp[i][i+1] = 1; 21 start = i; 22 sub_len = 2; 23 } 24 } 25 for(int j = 1; j < s_len;++j) 26 { 27 for(int i = 0; i < j - 1;++i) //dp[k][k] 和 dp[k][k+1] 作为base case 28 { 29 if(dp[i+1][j-1] == 1 && s[i] == s[j])//内一层子串是回文的,外层两个字符相同 30 { 31 if(j - i + 1 > sub_len) 32 { 33 start = i; 34 sub_len = j - i + 1; 35 } 36 dp[i][j] = 1;//标记 s[i:j] 为回文子串 37 } 38 } 39 } 40 return s.substr(start,sub_len); 41 }
Tips : 在记录子串信息时,记录 子串的起始位置 start 和 子串长度 sub_len 就行,最后再做截取操作。