LeetCode 5. 最长回文子串
我的LeetCode:https://leetcode-cn.com/u/ituring/
我的LeetCode刷题源码[GitHub]:https://github.com/izhoujie/Algorithmcii
LeetCode 5. 最长回文子串
题目
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
本题有三种思路解法:
- dp动态规划
- 中心扩展
- Manacher/马拉车算法
其中,前两种时间复杂度都是\(O(n^{2})\),最后一种是\(O(n)\)的,目前最优解;
思路1-dp动态规划
思路解析:dp的思路是若\([i] == [j]\),那么让\([i, j]\)是回文就必须有\([i + 1] == [j - 1]\);
即dp的动态方程为:
\[dp[i, j] = dp[i + 1, j - 1] and ([i] == [j] || j - i < 3)
\]
对于\(j - i < 3\)的解释:当最后的只剩单个字符时,作为回文的中心字符免验证;
算法复杂度:
- 时间复杂度: $ {\color{Magenta}{\Omicron\left(n^{2}\right)}} $
- 空间复杂度: $ {\color{Magenta}{\Omicron\left(n\right)}} $
思路2-中心扩展
思路解析:首先把字符串的所有连续相等的字符都压缩看为一个字符,那么对每一个字符尝试左右扩展回文长度即可;
核心思想:将所有连续相等的字符看为回文的最中心字符,避免了对奇偶数的区别计算;
算法复杂度:
- 时间复杂度: $ {\color{Magenta}{\Omicron\left(n^{2}\right)}} $
- 空间复杂度: $ {\color{Magenta}{\Omicron\left(1\right)}} $
思路3-Manacher/马拉车算法
待研究...
算法复杂度:
- 时间复杂度: $ {\color{Magenta}{\Omicron\left(n\right)}} $
- 空间复杂度: $ {\color{Magenta}{\Omicron\left(n\right)}} $
算法源码示例
package leetcode;
/**
* @author ZhouJie
* @date 2019年12月10日 下午2:30:25
* @Description: 5. 最长回文子串
*
*/
public class LeetCode_0005 {
}
class Solution_0005 {
/**
* @author ZhouJie
* @date 2019年12月10日 下午3:07:18
* @Description: TODO(方法简述)
* @return String
* @UpdateUser-UpdateDate:[ZhouJie]-[2019年12月10日 下午3:07:18]
* @UpdateRemark:1-动态规划
*
*/
public String longestPalindrome_1(String s) {
int len = 0;
if (s == null || (len = s.length()) < 2) {
return s;
}
boolean[] p = new boolean[len];
int[] range = new int[2];
for (int i = len - 1; i >= 0; i--) {
for (int j = len - 1; j >= i; j--) {
// j-i考虑到 像OO和OHO最后中心的奇偶问题
p[j] = s.charAt(i) == s.charAt(j) && (j - i < 3 || p[j - 1]);
if (p[j] && (j - i > range[1] - range[0])) {
range[0] = i;
range[1] = j;
}
}
}
return s.substring(range[0], range[1] + 1);
}
/**
* @author ZhouJie
* @date 2019年12月10日 下午3:34:44
* @Description: TODO(方法简述)
* @return String
* @UpdateUser-UpdateDate:[ZhouJie]-[2019年12月10日 下午3:34:44]
* @UpdateRemark:2-中心扩展--优化后
*
*/
public String longestPalindrome_2(String s) {
if (s == null || s.length() < 2) {
return s;
}
int[] range = new int[2];
char[] cs = s.toCharArray();
for (int i = 0; i < cs.length; i++) {
// 把回文看成中间部分都是同一字符且左右对称,寻找下一个与当前字符不同的位置
i = fastMove(cs, i, range);
// 若剩余长度不足已知最大长度的一半时,直接跳出循环
// 剩余长度的下一个起始计算位置为i+1,以为i+1为中心的剩余最长回文为(length-1-(i+1))*2+1
// 即 (lenght-i-2)*2+1,一只的最大长度为range[1]-range[0]+1
// 所以判定条件为 (lenght-i-2)*2+1<range[1]-range[0]+1
// 即(lenght-i-2)*2<range[1]-range[0]
if ((cs.length - i - 2) * 2 < (range[1] - range[0])) {
break;
}
}
return s.substring(range[0], range[1] + 1);
}
private int fastMove(char[] cs, int low, int[] range) {
int high = low;
int len = cs.length;
// 寻找下一个与low不等的字符
while (high < len - 1 && cs[high + 1] == cs[low]) {
high++;
}
int nextI = high;
// 开始校验左右扩散校验
while (low > 0 && high < len - 1 && cs[low - 1] == cs[high + 1]) {
low--;
high++;
}
if (high - low > range[1] - range[0]) {
range[0] = low;
range[1] = high;
}
return nextI;
}
/**
* @author: ZhouJie
* @date: 2020年5月22日 下午9:47:36
* @param: @param s
* @param: @return
* @return: String
* @Description: 3-Manacher算法--待研究
*
*/
public String longestPalindrome_3(String s) {
return null;
}
}
Talk is cheap. Show me the code.