Description:
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000. Do it now!
方案1: Longest Common Substring(类似于求最长公共子串问题)
想法:S‘为S的转置,求S和S’的最长公共子串
例子:S = “caba“, S’= “abac”. -> SS = “aba”
产生的错误:
例子:S = “aacdfgdcaa“, S’= “aacdgfdcaa“. 即当S的其它部分出现非回文子串的转置时,会产生错误,此时输出 SS = “abcad”
解决方案:增加判断条件,候选子串的索引必须与其转置的索引相同
\( i + j + 2 - a[i][j] == len \)实现代码:
public String longestPalindrome(String s) { if(s.length() <= 1) return s; if(s.length() == 2) return s.charAt(0) == s.charAt(1) ? s : s.substring(0, 1); int max_i = 0, max_len = 0; int len = s.length(); int[][] a = new int[len][len]; for(int i = 0; i < len; i++) { a[i][0] = s.charAt(len - 1) == s.charAt(i) ? 1 : 0; } for(int i = 0; i < len; i++) { a[0][i] = s.charAt(0) == s.charAt(len - i - 1) ? 1 : 0; } for(int i = 1; i < len; i++) { for(int j = 1; j < len; j++) { if (s.charAt(i) == s.charAt(len-j-1)) { a[i][j] = a[i-1][j-1] + 1; if (i + j + 2 - a[i][j] == len && a[i][j] > max_len) { max_i = i; max_len = a[i][j]; } } else { a[i][j] = 0; } } } return s.substring(max_i - max_len + 1, max_i + 1); }
时间复杂度:\(O(n^2)\),空间复杂度:\(O(n^2)\)
方案2: Brute Force(暴力解法)
- 想法:很明显,就是枚举全部可能的子串,验证它是否为回文串。
- 时间复杂度:\(O(n^3)\)
假设 \(n\) = 输入串的长度,那么它的子串数为 \(\frac{n*(n-1)}{2}\),每一次验证需要 \(O(n)\) 时间
- 空间复杂度:\(O(1)\)
方案3: Dynamic Programming(动态规划)
想法:为了改进暴力解法,我们需要观察如何在验证回文数时避免不必要的重复计算。
例子:”bab”是回文数,那么只要首位字母相等,那么”babab”一定也是。
$$ define P(i, j)= \begin{cases} true,&\text{if the substring is a palindrome}\\false,&\text{otherwise} \end{cases} $$则,\( P(i, j) = (P(i+1, j-1) \; and \; S_i = S_j) \)
初始化为,\( P(i, i) = true\),\( P(i, i+1) = (S_i = S_{i+1}) \)代码实现:
public String longestPalindrome(String s) { if(s.length() <= 1) return s; int max_i = 0, max_j = 0; int len = s.length(); boolean[][] a = new boolean[len][len]; a[len-1][len-1] = true; for(int i = len-2; i >= 0; i--) { a[i][i] = true; for(int j = i+1; j < len; j++) { a[i][j] = s.charAt(i) == s.charAt(j) && (j==i+1 || a[i+1][j-1]); if(a[i][j] && j - i > max_j - max_i) { max_j = j; max_i = i; } } } return s.substring(max_i, max_j + 1); }
时间复杂度:\(O(n^2)\),空间复杂度:\(O(n^2)\)
方案4: Expand Around Center(中心扩展法)
- 想法:每个回文串都有一个中心,一个长度为 $n$ 的字符串可能的中心点有 $2n-1$ 个。通过中心向两边扩展,验证扩展的字符串是否为回文。
例子:”aba”的中心是”b”,”abba”的中心是2个b之间。则字符串的可能中心数 = n(字符数) + (n-1)(字符间的空位数)
- 实现代码:
public String longestPalindrome(String s) { if(s.length() <= 1) return s; int max_i = 0, max_j = 0; for (int i = 0; i < s.length(); i++) { int len1 = expandAroundCenter(s, i, i); int len2 = expandAroundCenter(s, i, i+1); int len = len1 > len2 ? len1 : len2; if(len > max_j - max_i) { max_i = i - (len-1)/2; max_j = max_i + len; } } return s.substring(max_i, max_j); } int expandAroundCenter(String s, int l, int r) { while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { l--; r++; } return r - l - 1; }
- 时间复杂度:\(O(n^2)\),空间复杂度:\(O(1)\)
方案5: Manacher’s Algorithm(马拉车算法)
- 算法思想 - 这篇介绍的很详细,在线性时间复杂度内求出一个字符串的最长回文字串
- 实现代码:
public String longestPalindrome(String s) { char[] T = new char[s.length()*2 + 1]; int[] Len = new int[T.length]; // p0表示最长回文子串的中心位索引,p表示最长回文子串的最远字符索引 int p0 = 0, p = 0; // aaaba -> #a#a#a#b#a // 由于除了空字符以外的在s中也可能会出现,在这里不进行赋值默认为空字符,但是注释中为了方便会用#代替空字符 for(int i = 0; i < s.length(); i++){ T[2*i+1] = s.charAt(i); } // 0索引处永远为'#' Len[0] = 1; for(int i = 1; i < T.length; i++){ // 算法的核心步骤 Len[i] = i < p ? Math.min(Len[2*p0 - i], p-i+1) : 1; while(i-Len[i] >= 0 && i+Len[i] < T.length && T[i-Len[i]] == T[i+Len[i]]) { Len[i]++; } // 这里用 > 代替 >= 会节省算法时间 if (Len[i] > p-p0+1) { p0 = i; p = i + Len[i] - 1; } } return s.substring((2*p0-p)/2, p/2); }
- 时间复杂度:\(O(n)\),空间复杂度:\(O(n)\)
- Post link: http://yoursite.com/leetcode/5-LCS/
- Copyright Notice: All articles in this blog are licensed under unless stating additionally.
若您想及时得到回复提醒,建议跳转 GitHub Issues 评论。
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues