最长回文子串

问题描述:

在一个字符串中s,找到最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

输入:s = "babad"

输出:"bab"

解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"

输出:"bb"

提示:

  • 1 <= s.length <= 1000

  • s 仅由数字和英文字母组成

关键词1: 回文串  

回文串是一种特殊的字符串,从左往右读的顺序和从右往左读的顺序是一样的。如下图。

关键词2: 找到子串

子串是在整个字符串中截取的长度不等的连续字符串。如下图。

关键词3: 最长

任务描述:

找到一个子串,该子串为回文串。并且该子串是所有回文子串中,长度最长的。

 任务1:找到子串

在一整串字符中,用滑动窗口截取一段连续字符。但是,窗口的滑动方式对应了不同的时间复杂度。

滑动方法1:将窗口左端固定,滑动右端。或者将窗口右端固定,滑动左端。

      由于左固右滑和右固左滑的方式一样。所以,以下只描述左固右滑的具体过程。

      1、找到一个固定的左端i

      2、向右移动一格,找到右端j

      3、得到字符串s[i.....j]

      4、重复步骤2,得到所有以i为左端的字符串

      5、向右移动1格,得到新的左端i+1,重复步骤2,得到所有以i+1位左端的字符串

      6、重复步骤5,得到整个子串可拆分的子串。

代码如下:

 for(int i=0;i<s.size();i++)
 {  
    for(int j=1;j<s.size()-i+1;j++)
       {
         string tempstr=s.substr(i,j);//用substr函数得到,以i为起点,长度为j的子串
         //任务2:判断是否回文
         //任务3:判断回文子串长度是否最大
        }
  }
 滑动方法2:将窗口中心固定,向左或者向右滑动(中心扩散法)

       1、找到一个固定的中心i

       2、向左扩散寻找符合要求的字符。若找到,加入子串,并继续向左扩散;若未找到,则停止向左扩散寻找

       3、同样,向右扩散寻找符合要求的字符。若找到,加入子串,并继续向右扩散;若未找到,则停止向右扩散

      4、同时向两边扩散。如果符合要求,同时将两边字符加入子串,若不符合,则停止扩散。最终得到以i为中心的子串

     5、向右移动1格中心点,得到新的中心点i+1,重复步骤2,得到以i+1为中心点的子串

     6、重复步骤5,得到所有符合要求的子串

代码如下:

for(int i=0;i<n;i++)  //固定i为窗口中心
{
  int left=i-1;  //定义一个初步的左窗口
  int right=i+1;  //定义一个初步的右窗口
  
  while(left>=0&&s[left]==s[i]) //满足向左扩散的条件
    {left--; //窗口左移
    //其余任务的处理}
  while(right<=n-1&&s[right]==s[i]) //满足向右扩散的条件
    {right++; //窗口右移
     //其余任务的处理}

  while(left>=0&&right<=n-1&&s[left]==s[right])//满足同时左右扩散的条件
    {left--;right++;  //左右窗口同时移动
    //其余任务的处理}
}

 任务2:判断是否回文

判断一个子串是否满足回文特性。

方法1:从定义出发(即,从左往右读的顺序和从右往左读的顺序是一样的
将字符串倒转过来,相互比对。若相同,则是回文串;如不同,则不是回文串。

该方法简单、暴力,对于所有滑动方式得到的子串都合适。

代码如下:

//通过任务1,得到了字符串
string fanzhuan=tempstr;  //定义一个字符串用来存储翻转后的子串
reverse(fanzhuan.begin(),fanzhuan.end()); //翻转字符串
if(fanzhuan==tempstr)  //比对翻转前后的子串
{
  //若比对相同,则该字符串是回文串,然后进行任务3的处理
}
方法2:中心扩散法(以回文串s为中心,判断向两边扩散后的字符串是否同样回文)

该方法仅适用于以中心扩散为窗口滑动方式的子串。

对于已经是回文串的子串s来说,若子串前后字符均相同,则扩散后的子串肯定也是回文串。如不同,则扩散后的子串肯定不是回文串。

但是,只向两边扩散的方法,对于如下图所示的初始子串{a,b,b,a},肯定是不行的。

 因为只向两边扩散的方法只会判定{b,b,a}是非回文,而忽略掉{b,b}子串。

所以,需要对中心i向左或者向右扩散,找到重复的字符,将其加入回文子串中,以防止上述情况发生。

但是,单边扩散仅限于回文中心的初始位置。因为回文中心是重复字符的话,该中心必定符合回文性质。如果重复字符出现在字符串非中心位置,用两边同时扩散的检测方法,同样可以判断出来。

      1、找到一个固定的中心i

       2、向左扩散寻找与s[i]相同的字符。若找到,加入子串,并继续向左扩散;直到遇到与s[i]不相同的字符

       3、同样,向右扩散寻找与s[i]相同的字符。若找到,加入子串,并继续向右扩散;直到遇到与s[i]不相同的字符

      4、同时向两边扩散。如果s[left]==s[right],同时将两边字符加入子串,并继续向两边扩散;直到两端字符不相同。最终得到以i为中心的子串

     5、向右移动1格中心点,得到新的中心点i+1,重复步骤2,得到以i+1为中心点的子串

     6、重复步骤5,得到所有符合要求的子串

代码如下:
//通过窗口中心的移动,得到初始的字符s[i]
//以下是判断,以s[i]为中心的字符串是否是回文,的条件
while(left>=0&&s[left]==s[i]) //若左端left与i的字符相同,且滑动窗口未超出左边界
{
   left--; //窗口左移
 //任务3的处理
}
while(right<=n-1&&s[right]==s[i]) //若右端right与i的字符相同,且滑动窗口未超出右边界
{
right++; //窗口右移
//任务3的处理
}
while(left>=0&&right<=n-1&&s[left]==s[right])//若左右两端的字符相同,且两端窗口未超出边界
{
 left--;right++;//同时向两边扩散
//进行任务3的处理
}
      

 任务3:判断长度是否最长

在得到一个回文子串后,需要判断该子串是否是长度最长的子串。若是,则返回该子串;若不是,则跳过。

 维护两个全局变量,maxstring和maxlen,记录最长子串和其长度。

每次得到回文子串,计算长度。若长度超过maxlen,则更新maxstring和maxlen;若未超过,则跳过,寻找下一个子串。

代码如下:

//通过任务1、2,得到回文子串
strlen=tempstr.size(); //得到该子串的长度
if(maxstrlen<strlen)  //若该长度大于已经被记录的最长子串长度
{
  maxstring = tempstr; //更新最长子串
  maxstrlen=strlen;  //更新最长子串长度
}
//若未超过记录的最长子串,则跳过

完整的处理过程和代码展示如下:

(以中心扩散法为例)

class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.size();//n为字符串长度
        int maxlen=0;
        int maxstr=0;//最长回文子串的起始位置
        for(int i=0;i<n;i++)
        {
            int left=i-1;
            int right=i+1;
            int len=1;//以i为中心扩散时的子串长度
            while(left>=0&&s[left]==s[i]) {left--;len++;}
            while(right<=n-1&&s[right]==s[i]) {right++;len++;}
            while(left>=0&&right<=n-1&&s[left]==s[right])
            {left--;right++;len+=2;}
            if(maxlen<len)
            {maxlen=len;maxstr=left+1;}
        }
        string maxstring;
        maxstring=s.substr(maxstr,maxlen);
        return maxstring;
    }
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值