主要参考自力扣官网解法
1.最长回文子串(5)
给你一个字符串 s
,找到 s
中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
方法一:动态规划
思路与算法:看了力扣的官方解法,才逐渐理解这个代码.
这是官方思路,我在这里搬运一下,阐述一下我的理解。首先从我是从代码的角度进行分析。先将字符串分割成字符数组,然后如果数组大小小于二,这时根本无需判断,他只有一个子串,就是他自己。所以直接return,如果大于等于二,直接向下进行,初始化最大长度,开始索引,二维数组。然后开始循环,先循环子串长度,从二开始然后子串里再进行索引i循环,从一开始,因为子串长度L=j-i+1;所以我们可求出右边界j,如果j超出最大长度len,即索引越界,此时跳出循环。如果没有,则进入关键代码。根据动态规划方程,我们先判断此时数组【i】和数组【j】是否数值一样,如果不一样自然是false,否则在判断是否子串长度为1或2,如果是,则二维数组对应为true(此时主要指的是子串长度为2的),否则p[i][j] = p[i + 1][j - 1];然后再判断当前得到的回文子串长度是否为最大值。
class Solution {
public String longestPalindrome(String s) {
char[] charArray = s.toCharArray();
int len = charArray.length;
if (len < 2) {
return s;
}
int maxLen = 1,begin = 0;
// dp[i][j] 表示 s[i..j] 是否是回文串
boolean[][] p = new boolean[len][len];
// 初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < len; i++) {
p[i][i] = true;
}
// 递推开始
// 先枚举子串长度
for (int L = 2; L <= len; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (int i = 0; i < len; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
int j = L + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= len) {
break;
}
if (charArray[i] != charArray[j]) {
p[i][j] = false;
} else {
if (j - i < 3) {
p[i][j] = true;
} else {
p[i][j] = p[i + 1][j - 1];
}
}
// 只要 p[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (p[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
}
时间复杂度:O(n2)
空间复杂度:O(n2)
方法二:中心扩展算法
思路与算法
在方法一的基础上拓展,中心扩散思想较为简单。
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
public int expandAroundCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return right - left - 1;
}
}
时间复杂度:O(n2)
空间复杂度:O(1)
方法三:Manacher 算法
为了表述方便,我们定义一个新概念臂长,表示中心扩展算法向外扩展的长度。如果一个位置的最大回文字符串长度为 2 * length + 1 ,其臂长为 length。
下面的讨论只涉及长度为奇数的回文字符串。长度为偶数的回文字符串我们将会在最后与长度为奇数的情况统一起来。
思路与算法
参考https://www.cnblogs.com/cloudplankroader/p/10988844.html
回文半径和回文直径:因为处理后回文字符串的长度一定是奇数,所以回文半径是包括回文中心在内的回文子串的一半的长度,回文直径则是回文半径的2倍减1。比如对于字符串 "aba",在字符 'b' 处的回文半径就是2,回文直径就是3。
最右回文边界R(right):在遍历字符串时,每个字符遍历出的最长回文子串都会有个右边界,而R则是所有已知右边界中最靠右的位置,也就是说R的值是只增不减的。
回文中心C(j):取得当前R的第一次更新时的回文中心。由此可见R和C时伴生的。
半径数组(arm_len):这个数组记录了原字符串中每一个字符对应的最长回文半径。
class Solution {
public String longestPalindrome(String s) {
int start = 0, end = -1;
StringBuffer t = new StringBuffer("#");
for (int i = 0; i < s.length(); ++i) {
t.append(s.charAt(i));
t.append('#');
}
t.append('#');
s = t.toString();
List<Integer> arm_len = new ArrayList<Integer>();
int right = -1, j = -1;
for (int i = 0; i < s.length(); ++i) {
int cur_arm_len;
if (right >= i) {
int i_sym = j * 2 - i;
int min_arm_len = Math.min(arm_len.get(i_sym), right - i);
cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len);
} else {
cur_arm_len = expand(s, i, i);
}
arm_len.add(cur_arm_len);
if (i + cur_arm_len > right) {
j = i;
right = i + cur_arm_len;
}
if (cur_arm_len * 2 + 1 > end - start) {
start = i - cur_arm_len;
end = i + cur_arm_len;
}
}
StringBuffer ans = new StringBuffer();
for (int i = start; i <= end; ++i) {
if (s.charAt(i) != '#') {
ans.append(s.charAt(i));
}
}
return ans.toString();
}
public int expand(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return (right - left - 2) / 2;
}
}
时间复杂度:O(n)
空间复杂度:O(n)