题目
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000
。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思考过程
- 首先需要查找最长的回文,必须要有一个变量
max
记录当前存在的最长回文长度。 - 第二步,我们遍历给定
string
中的每个字符,查找以这个字符为中心的最长回文长度,如果当前回文长度大于max
,更新max
- 循环进行第二步,只到考察完
string
中的每一个字符
名词
考察:表示判断某个字符处的最大回文长度
更多思考
- 相同字符的思考
=================
abcdeedc
01234567
=================
当我们考察到第一个字符`e`的时候,我们不能去比较`3`和`5`的位置,因为`5`的位置字符也是`e`,这也是一种回文,所以我们应该直接比较`3`和`6`的位置。并且我们也不需要再考察下一个`e`(第`5`位的`e`),因为已经包含在了上一次考察中(`4`位置的`e`)
- 有没有不需要考察的情况
- 如果我们已经找到一个比较长的回文,并且当前考察的字符很接近字符串的结尾或者开始,那么我们可以判断,当前字符到开始/结尾处的所有字符
// 这里是判断,是否查询出的maxStr已经是最大
if ((i + 1) * 2 - 1 < temp_length) {
......
}
- 优先判断最远处的字符
…
实现
从左往右依次查找
public class Solution {
public String longestPalindrome(String s) {
// 构建最长字符串实例,默认长度为0
int[] max = new int[2];
char[] charArray = s.toCharArray();
int length = charArray.length;
// 处理空字符串的情况
if (length == 0) {
return "";
}
for (int i = 0; i < length; i++) {
int temp_length = max[1] - max[0] + 1;
// 这里是判断,是否查询出的maxStr已经是最大
if (((length - i) * 2 - 1) < temp_length) {
break;
}
// 这里先判断,是否有可能比已经存在的最长的回文更长
if (mayMax(charArray, i, temp_length)) {
// 0 存储开始的index, 1 存储结束的index
int[] temp_max = new int[2];
// 向左查找和当前下标相同的index
int left_index = leftRepeatStrMaxLong(charArray, i);
// 向右查找和当前下标相同的index
int right_index = rightRepeatStrMaxLong(charArray, i);
temp_max[0] = left_index;
temp_max[1] = right_index;
// 指定两个index,向两边依次查找
getMaxStr(charArray, temp_max);
// 如果找到更长的回文就更新max
if (temp_max[1] - temp_max[0] + 1 > temp_length) {
max = temp_max;
}
// 根据right_index设置需要考察的下一个下标
i = right_index;
}
}
return s.substring(max[0], max[1] + 1);
}
public int rightRepeatStrMaxLong(char[] source, int i) {
int j;
for (j = i + 1; j < source.length; j++) {
if (source[j] != source[i]) {
break;
}
}
return j - 1;
}
public int leftRepeatStrMaxLong(char[] source, int i) {
int j;
for (j = i - 1; j >= 0; j--) {
if (source[j] != source[i]) {
break;
}
}
return j + 1;
}
public Boolean mayMax(char[] source, int i, int length) {
// 如果长度小于2,回文肯定有机会更长
if (length >= 2) {
// 当前最长回文长度的一半
int half_length = length / 2;
// 判断是否超出长度,如果超出长度,说明不可能是最大
// i + half_length 表示 和当前最长回文长度一样长的回文的right index
// tips:可以思考这里为什么 right_index 不能设置为 i + half_length + 1
int right_index = i + half_length;
// i - half_length 表示 和当前最长回文长度一样长的回文的left index
int left_index = i - half_length;
if (right_index >= source.length - 1 || left_index <= 0) {
return false;
}
// 如果数组不会越界,就尝试着测试最远处的两对元素是否相同
// 第一部分情况是常见的情况
// 第二部分的判断校验的是偶数长度回文的方式
// ==================================
// abadeed
// 0123456
// ==================================
// 假如 i == 4,此时 half_length = 1, left_index = 3, right_index = 5, 此时第一部分的判断是不成立的
// 第二部分的判断成立,并且结果页可能是一段回文
return ((source[left_index] ^ source[right_index]) + (source[left_index + 1] ^ source[right_index - 1])) == 0
|| ((source[left_index] ^ source[right_index + 1]) + (source[left_index + 1] ^ source[right_index])) == 0;
} else {
return true;
}
}
public void getMaxStr(char[] source, int[] currentMaxStr) {
// 设置新的
int left = currentMaxStr[0] - 1;
int right = currentMaxStr[1] + 1;
// 当对source的访问不越界 并且 source[left] == source[right],就进行下一对的元素比较
while (!(left < 0 || right > source.length - 1) && source[left] == source[right]) {
left--;
right++;
}
// 这里需要特殊处理一下left和right,因为当前对source进行了越界访问或者source[left] == source[right]
currentMaxStr[0] = left + 1;
currentMaxStr[1] = right - 1;
}
}
优化思路
- 从中间开始查找是不是能更快的找出比较长的回文,这样就有利于
mayMax()
方法的判断 - 简化方法,合理声明变量,传值
优化后的版本:
public class Solution {
/**
* 向左考察的索引
*/
private int left_index_rest = 0;
/**
* 向右考察的索引
*/
private int right_index_rest = 0;
/**
* length - 1
*/
private int total_index;
/**
* 每次考察字符时,使用的临时数组
*/
private int[] temp_max = new int[2];
private char[] charArray;
/**
* 当前出现的最大回文
*/
private int[] max = new int[2];
/**
* 当前查看的下标
*/
private int i;
/**
* temp_max[1] - temp_max[0] + 1
*/
private int current_length;
/**
* max[1] - max[0] + 1
*/
private int temp_length;
private void choiceIndex() {
if (left_index_rest >= 0) {
i = left_index_rest--;
} else {
i = right_index_rest++;
}
}
public String longestPalindrome(String s) {
// 构建最长字符串实例,默认长度为0
charArray = s.toCharArray();
int length = charArray.length;
// 处理空字符串的情况
if (length == 0) {
return "";
}
int choice_index = length / 2 - 1;
left_index_rest = choice_index;
right_index_rest = choice_index + 1;
total_index = length - 1;
i = left_index_rest;
temp_length = max[1] - max[0] + 1;
while (i < length && i >= 0) {
// 这里是判断,是否查询出的maxStr已经是最大
if ((i + 1) * 2 - 1 < temp_length) {
left_index_rest = -1;
choiceIndex();
continue;
}
if (((length - i) * 2 - 1) < temp_length) {
break;
}
// 这里先判断,是否有可能比已经存在的最长的回文更长
if (mayMax(i, temp_length)) {
// 0 存储开始的index, 1 存储结束的index
// 向左查找和当前下标相同的index
leftRepeatStrMaxLong(i);
// 向右查找和当前下标相同的index
rightRepeatStrMaxLong(i);
// 指定两个index,向两边依次查找
// 根据right_index设置需要考察的下一个下标
if (temp_max[0] < left_index_rest) {
left_index_rest = temp_max[0] - 1;
}
if (temp_max[1] > right_index_rest) {
right_index_rest = temp_max[1] + 1;
}
getMaxStr();
current_length = temp_max[1] - temp_max[0];
// 如果找到更长的回文就更新max
if (temp_max[1] - temp_max[0] >= temp_length) {
max[0] = temp_max[0];
max[1] = temp_max[1];
temp_length = max[1] - max[0] + 1;
}
}
choiceIndex();
}
return s.substring(max[0], max[1] + 1);
}
public void rightRepeatStrMaxLong(int i) {
int c = charArray[i];
int j = i + 1;
while (j < charArray.length && c == charArray[j]) {
j ++;
}
temp_max[1] = j - 1;
}
public void leftRepeatStrMaxLong(int i) {
int c = charArray[i];
int j = i - 1;
while (j >= 0 && c == charArray[j]) {
j--;
}
temp_max[0] = j + 1;
}
public Boolean mayMax(int i, int length) {
// 如果长度小于2,回文肯定有机会更长
if (length >= 2) {
// 当前最长回文长度的一半
int half_length = length / 2;
// 判断是否超出长度,如果超出长度,说明不可能是最大
// i + half_length 表示 和当前最长回文长度一样长的回文的right index
// tips:可以思考这里为什么 right_index 不能设置为 i + half_length + 1
int right_index = i + half_length;
// i - half_length 表示 和当前最长回文长度一样长的回文的left index
int left_index = i - half_length;
if (right_index >= charArray.length - 1 || left_index <= 0) {
return false;
}
// 如果数组不会越界,就尝试着测试最远处的两对元素是否相同
// 第一部分情况是常见的情况
// 第二部分的判断校验的是偶数长度回文的方式
// ==================================
// abadeed
// 0123456
// ==================================
// 假如 i == 4,此时 half_length = 1, left_index = 3, right_index = 5, 此时第一部分的判断是不成立的
// 第二部分的判断成立,并且结果页可能是一段回文
return ((charArray[left_index] == charArray[right_index]) && (charArray[left_index + 1]== charArray[right_index - 1]))
|| ((charArray[left_index] == charArray[right_index + 1]) && (charArray[left_index + 1] == charArray[right_index]));
} else {
return true;
}
}
public void getMaxStr() {
// 设置新的
int left = temp_max[0] - 1;
int right = temp_max[1] + 1;
// 当对source的访问不越界 并且 source[left] == source[right],就进行下一对的元素比较
while (!(left < 0 || right > charArray.length - 1) && charArray[left] == charArray[right]) {
left--;
right++;
}
// 这里需要特殊处理一下left和right,因为当前对source进行了越界访问或者source[left] == source[right]
temp_max[0] = left + 1;
temp_max[1] = right - 1;
}
}
运行
4ms - 5ms
,打败99%
如何考察更少的字符?
应该还有可能进一步优化,考察的数量
我的个人博客有空来坐坐
https://www.wangyanan.online