1.每日一句
五颜六色的生活,不应该乱七八糟的过
2.作者简介
🏡个人主页:XiaoXiaoChen-2716
📚学习专栏:力扣专栏
🕒发布日期:2022/10/5
『LeetCode|每日一题』最长回文子串
1.每日一题
2.解题思路(动态规划dp)
2.1 思路分析
首先看到此题,就想着找最长,那么就可以从长度为2开始慢慢加,于是想到了动态规划,因为可以在回文子串的基础上两边增加两个相同的字母也是回文子串
S1:用一个二维数组dp来存从i到j是否是回文子串,定义一个变量来存回文子串的最大长度,初始值为1,定义一个start来存最长回文子串的初始位置,初始值为0;
S2:首先得确定特殊情况,长度为1的都是回文子串,长度为2的只要字母相等也是回文子串;
S3:遍历字符串时,要注意遍历的顺序,首先从长度为2的开始(这里的2指的是字符串长度大于2的字串),所以得用一个循环来遍历长度,另一个循环则是遍历字符串左边界;
S4:在循环开始时,得先确定不出界,即字串的右边界不能大于等于字符串的长度,如果越界了就要退出循环;若不越界,此时开始判断左边界和右边界的字母是否相等,如果不相等,那么此时dp就应该等于0;如果相等,还得分情况讨论,如果此时右边界减去左边界的值小于3,那么这个范围的字符串肯定是回文子串(这个很容易知道),如果大于等于三,此范围的子串是否是回文子串取决于左边界加一到右边界减一这个范围的字串,如图所示:
S5:每次遍历完一次长度,都要进行一次判断,如果dp[i][j] == 1 && j - i + 1 >max,满足这个条件就要把最大长度max的值替换,并且重新记录开始值,直到找到长度最大的回文子串;
S6:最后用一个substring函数拼接从start到start + max范围的字符串就是最长回文字串
2.2 核心代码
for(int i = 2 ; i <= length ; i++){
for(int j = 0 ; j < length ; j++){
int p = i + j - 1; //确定边界
if(p >= length){
break;
}
if(cc[j] != cc[p]){
dp[j][p] = 0;
}else {
if(p - j < 3){
dp[j][p] = 1;
}else {
dp[j][p] = dp[j + 1][p - 1];
}
}
if(dp[j][p] == 1 && p - j + 1 > max){
max = p - j + 1;
start = j;
}
}
}
2.3 完整代码
class Solution {
public String longestPalindrome(String s) {
int length = s.length();
if(length == 1){
return s;
}else if(length == 2 && s.charAt(0) == s.charAt(1)){
return s;
}
int[][] dp = new int[length][length];
for(int i = 0 ; i < length ; i++){ //长度为1的都是回文子串
dp[i][i] = 1;
}
char[] cc = s.toCharArray();
int max = 1;
int start = 0;
for(int i = 2 ; i <= length ; i++){
for(int j = 0 ; j < length ; j++){
int p = i + j - 1; //确定边界
if(p >= length){
break;
}
if(cc[j] != cc[p]){
dp[j][p] = 0;
}else {
if(p - j < 3){
dp[j][p] = 1;
}else {
dp[j][p] = dp[j + 1][p - 1];
}
}
if(dp[j][p] == 1 && p - j + 1 > max){
max = p - j + 1;
start = j;
}
}
}
return s.substring(start , start + max);
}
}
2.4 运行结果
可以看出来,不管是从时间角度还是空间角度都是消耗比较大的,且都是O() ,那么请看下一种解题方法
3.解题思路(扩展方法)
3.1 思路分析
我们从动态规划法中可以看出来动态转移方程,无非就三种:
- 长度为1
- 长度为2且两字母相等
- 当左边界等于右边界并且中间范围内的字串也是回文子串时
满足上面三种情况的都是回文子串,由此可以看出dp[i][j]<---dp[i + 1][j - 1]<---dp[i + 2][j - 2]等等,从此可以看出,我们可以从每一种边界情况开始扩展,也可以得出所有字符串情况所对应的答案,边界情况就是长度为1和长度为2的情况;
也就是说,我们枚举每一种边界情况,如果是回文子串就可以向两边扩展,如果不是那么就肯定不能继续扩展了
S1:首先同样是特殊情况处理,也就是s的长度是1或者2的情况;
S2:我们用start和end来记录回文子串的起始位置和结束位置,扩展的这个操作我们可以写成一个函数,用来返回从i到j最长回文字串的长度,当然i也可以等于j;
S3:首先字串的开始和结束位置都是0,即start = end = 0,开始遍历时,用一个maxLen来记录从第i个元素扩展和从第i开始到第i + 1结束中的最大的长度,也就是两者扩展最长的长度;(这里解释一下,为什么是第i个元素以及第i开始到第i + 1结束呢,因为开始我们说了,边界情况就是长度为1和长度为2两种情况,这里就能理解了)
S4:如果此时扩展的最大长度大于当前的end - start,说明有更长的字串,所以此时的start应该等于i - (maxLen - 1) / 2,end应该等于i + maxLen / 2,为什么除以2就i是因为扩展是两边的,每次长度都加了2,所以要除以2,读者可以举个例子就能知道了;
S5:最后拼接字符串,起始位置是start,结束位置是end + 1
3.2 核心代码(扩展法)
for(int i = 0 ; i < s.length() ; i++){
int a = kuozhan(s , i , i);
int b = kuozhan(s , i , i + 1);
int maxLen = Math.max(a , b);
if(maxLen > end - start){
start = i - (maxLen - 1) / 2;
end = i + maxLen / 2;
}
}
3.3 扩展函数
private int kuozhan(String s , int start , int end){
while(start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)){
start--;
end++;
}
return end - start - 1;
}
class Solution {
public String longestPalindrome(String s) {
int length = s.length();
if(length == 1){
return s;
}else if(length == 2 && s.charAt(0) == s.charAt(1)){
return s;
}
int start = 0 , end = 0;
for(int i = 0 ; i < s.length() ; i++){
int a = kuozhan(s , i , i);
int b = kuozhan(s , i , i + 1);
int maxLen = Math.max(a , b);
if(maxLen > end - start){
start = i - (maxLen - 1) / 2;
end = i + maxLen / 2;
}
}
return s.substring(start , end + 1);
}
private int kuozhan(String s , int start , int end){
while(start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)){
start--;
end++;
}
return end - start - 1;
}
}
3.5 运行结果
通过对比可以发现,扩展发没有用到数组,空间复杂度是O(1),时间复杂度还是O() ,因为扩展函数每次扩展最多的时候也是O(n)
🍁 类似题目推荐:
如果文章对各位大佬有帮助就支持一下噢,新手尝试,不好的地方请各位大佬多多指教!