LeetCode 第5题 Longest Palindromic Substring 最长回文子串

  这道题实在是很经典了,解法也很多,我只写出了暴力解法-_-||,功力不够啊,把这些解法都学会总结一下,看看是怎么一步一步优化过来的。

题目描述

  给定一个子串,找出其中最长的回文子串,注意子串一定要是连续的。

题目解法

暴力解法

  我的暴力解法思路很简单,从大到小枚举可能的长度 ( l e n , 1 ) (len,1) (len,1),然后对每个长度枚举起点,然后判断枚举是否为回文串,如果是则终止枚举然后输出。时间复杂度为 O ( n 3 ) O(n^3) O(n3),最低效的做法,并没用上上一个计算出来的回文串结果。

class Solution {
public:
    string longestPalindrome(string s) {
        //字符串长度
        int len = s.size();
        
        string ans;
        for(int max_len = len;max_len>=1;max_len--)
        {   
	        for(int start=0;start+max_len<=len;start++)
	            {
	                string substr = s.substr(start,max_len);
	                if(judge(substr)) {return substr;} 
	            }
        }
        
        return s;
    }
    
    bool judge(string s)
    {
        int len = s.size();
        
        for(int i=0,j=len-1;i<j;i++,j--)
            if(s[i]!=s[j]) return false;
        
        return true;
    }
};

动态规划解法

  暴力解法中,我们可以避免大量的重复判断一个串是否为回文串的不必要计算,动态规划的思想为定义一个数组:
d p [ i ] [ j ] , 表 示 从 i 开 始 到 j 结 束 的 子 串 是 否 为 回 文 串 dp[i][j],表示从i开始到j结束的子串是否为回文串 dp[i][j],ij
这样我们可以得到如下的递推式: d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] &amp; &amp; s [ i ] = = s [ j ] dp[i][j]=dp[i+1][j-1]\&amp;\&amp;s[i]==s[j] dp[i][j]=dp[i+1][j1]&&s[i]==s[j]

动态规划还需要一个初始条件,不难想到为:
d p [ i ] [ i ] = t r u e dp[i][i]=true dp[i][i]=true d p [ i ] [ i + 1 ] = s [ i ] = = s [ i + 1 ] dp[i][i+1]=s[i]==s[i+1] dp[i][i+1]=s[i]==s[i+1]
这样我们就可以通过长度为1的结果,长度为2的结果,从而推出长度为3,4,5…的结果,动态规划时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    string longestPalindrome(string s) {
      int n = s.length();
  int longestBegin = 0;
  int maxLen = 1;
  bool table[1000][1000] = {false};
  for (int i = 0; i < n; i++) {
    table[i][i] = true;
  }
  for (int i = 0; i < n-1; i++) {
    if (s[i] == s[i+1]) {
      table[i][i+1] = true;
      longestBegin = i;
      maxLen = 2;
    }
  }
  for (int len = 3; len <= n; len++) {
    for (int i = 0; i < n-len+1; i++) {
      int j = i+len-1;
      if (s[i] == s[j] && table[i+1][j-1]) {
        table[i][j] = true;
        longestBegin = i;
        maxLen = len;
      }
    }
  }
  return s.substr(longestBegin, maxLen);
    }
};

从中间开始向两边扩展的办法

  我们可以通过进一步优化,使得时间复杂度为 O ( n 2 ) O(n^2) O(n2)、空间复杂度为 O ( 1 ) O(1) O(1),回文串都是从中间向两边对称扩展的,所以我们可以枚举回文子串中间的位置,一共有 2 N − 1 2N-1 2N1种情况,为什么不是 N N N呢?注意到,当一个回文串长度为偶数时,其实我们是从两个字符的中间开始枚举的。注意这里处理枚举情况的技巧.

class Solution {
public:
    string expanding_longeststring(string s,int c1,int c2)
    {
        int l = c1,r = c2;
        int len = s.size();
        for(;l>=0&&r<len&&s[l]==s[r];l--,r++);
        
        return s.substr(l+1,r-l-1); 
    }
    
    string longestPalindrome(string s) {
        
        int len = s.size();
        if(len==0) return s;
        string ans;
        ans = s.substr(0,1);
        
        string temp;
        for(int i = 0;i<len;i++)
        {
            temp = expanding_longeststring(s,i,i);
            
            if(temp.size()>ans.size())
            {
                ans = temp;
            }
            
            temp = expanding_longeststring(s,i,i+1);
            
            if(temp.size()>ans.size())
            {
                ans = temp;
            }
        }
        
        return ans;
    }
};

Manacher’s 算法

  马拉车算法,(⊙o⊙)…,额这个名字挺不错的,这么炫酷的算法不学会简直对不起自己,这个算法的时间复杂度为 O ( n ) O(n) O(n)、空间复杂度为 O ( n ) O(n) O(n)。下面总结一下这个算法。
  该算法将原来的字符串进行扩展,将两边和每个字符的中间插入‘#’,例如S = “abaaba”, T = “#a#b#a#a#b#a#”。
  然后算法采用了一个数组 P P P,来保存当前位置回文串的最大长度,即 P [ i ] P[i] P[i]保存 T T T中以第 i i i个位置为中心的向左或向右最大能扩展的回文子串长度。也就是 T i − P [ i ] . . . . . T i + P [ i ] T_{i-P[i]}.....T_{i+P[i]} TiP[i].....Ti+P[i]为回文子串,例如下面的结果。不难发现, P [ i ] P[i] P[i]即为原字符串中以 i i i为中心的最长回文子串长度。

在这里插入图片描述
  不难看出, P P P数组具有很强的对称性,那我们能否通过前面计算的 P [ i ] P[i] P[i]从而快速得到后面的 P [ i ] P[i] P[i]呢?答案是可以,算法通过维护两个中间变量 C 、 R C、R CR来快速计算各个 P [ i ] P[i] P[i],其中 C C C代表当前最长回文子串的中心点, R R R表示该子串的右边界,其实有 R = C + P [ i ] R=C+P[i] R=C+P[i],下面来分析一下各种情况:
在这里插入图片描述
在这里插入图片描述
  上图中假设我们已经计算出来了 P [ 0 ] P[0] P[0] P [ 11 ] P[11] P[11],当前 C = 11 , R = 20 , P [ C ] = 9 C=11,R=20,P[C]=9 C=11,R=20,P[C]=9,假设此时 i = 13 i=13 i=13,也就是现在我们要计算 P [ 13 ] P[13] P[13]的值,其中 i = 13 i=13 i=13关于 C C C的对称点为 i ′ = 9 i&#x27;=9 i=9,通过对称性,像上图绿线所示,显然有 P [ i ] = P [ i ′ ] P[i]=P[i&#x27;] P[i]=P[i],所以我们通过这种对称性就可以快速得到 P [ i ] P[i] P[i]的值。那么是否所有的情况都满足这种性质呢?并不是,下面我们来看另外一种情况:
在这里插入图片描述
在这里插入图片描述
  假设我们现在要计算 i = 15 i=15 i=15的值,那么根据上面的分析,是不是有 P [ 15 ] = P [ 7 ] = 7 P[15]=P[7]=7 P[15]=P[7]=7呢?显然,并不是。原因是,我们看到由于 P [ i ′ ] = 7 P[i&#x27;]=7 P[i]=7红色线部分已经超出了我们当前最长回文子串的左边界,同理我们的 P [ i ] P[i] P[i]的对应部分也超出了右边部分,所以超出的部分并不满足对称性,我们不能计算。现在我们知道了, i i i至少能扩展到右边界 R R R,也就是 P [ i ] ≥ 5 P[i]\geq 5 P[i]5,剩下还能扩展多少就需要我们自己判断了, P [ 21 ] ! = P [ 9 ] P[21]!=P[9] P[21]!=P[9],所以有 P [ i ] = 5 P[i]=5 P[i]=5
  总结一下,分以下情况:

  • i &lt; R i&lt;R i<R
    • R − i ≤ P [ i ] R-i\leq P[i] RiP[i],这种情况下我们的对称区域不会超过右边界,所以我们直接令 P [ i ] = P [ i ′ ] P[i]=P[i&#x27;] P[i]=P[i],其中 i ′ i&#x27; i i i i关于 C C C的对称点
    • R − i &gt; P [ i ] R-i &gt;P[i] Ri>P[i],这种情况下我们的对称区域超过了右边界,只知道 P [ i ] ≥ R − i P[i]\geq R-i P[i]Ri,还能扩展多少需要我们自己接下来进行判断
  • i ≥ R i\geq R iR
    • 这种情况下超出了我们的先验知识,我们只能令 P [ i ] = 0 P[i]=0 P[i]=0,然后自己接下来一步步判断最大能伸展多少

这一步判断关键代码如下:

i_mirror = 2*C-i;
P[i] = (R>i)?min(R-i,P[i_mirror]):0;

  现在最后一步就是我们怎么更新 C 、 R C、R CR,很简单,当我们计算 i i i位置时,发现将 R R R进行了扩展,那我们就让 C = i , R = i + P [ i ] C=i,R=i+P[i] C=i,R=i+P[i]。最后我们只需要扫描一遍 P P P数组,找出其中索引和最大值就OK了。
  扩展 R R R最多需要 N N N次,而枚举和测试每个中心 C C C,也最多需要 N N N次。所以算法一共需要 2 N 2N 2N步,时间复杂度为 O ( N ) O(N) O(N)

class Solution {
public:
   string preprocessing(string s)
   {
       int len = s.size();
       string str;
       if(len==0) str = "$^";
       else str="$";
       
       for(int i=0;i<len;i++)
           str+='#'+s.substr(i,1);
       
       str+="#^";
       
       return str;
   }
    
    string longestPalindrome(string s) {
        
        string str = preprocessing(s);
        int len = str.size();
        int C=0,R =0;
        //申请保存中间变量的数组
        int *P = new int[len+10];
        
        for(int i=1;i<len;i++)
        {
            int mirror_i = 2*C - i;
            //要在边界区域内
            P[i] = (R>i)?min(P[mirror_i],(R-i)):0;
            
            //下面继续扩充P[i]
            while(str[i-P[i]-1]==str[i+P[i]+1]) P[i]++;
            
            //下面更新C,R
            if(i+P[i]>R)
            {
                C=i;
                R = i+P[i];
            }
            
        }
        
        int max_length = 0;
        int idx = 0;
        //寻找最大值
        for(int i=1;i<len;i++)
        {
            if(P[i]>max_length)
            {
                max_length = P[i];
                idx = i;
            }
        }
        delete P;
        return s.substr((idx-max_length)/2,max_length);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,有三种方法可以解决LeetCode上的最长回文子串。 方法一是使用扩展中心法优化,即从左向右遍历字符串,找到连续相同字符组成的子串作为扩展中心,然后从该中心向左右扩展,找到最长的回文子串。这个方法的时间复杂度为O(n²)。\[1\] 方法二是直接循环字符串,判断子串是否是回文子串,然后得到最长回文子串。这个方法的时间复杂度为O(n³),效率较低。\[2\] 方法三是双层for循环遍历所有子串可能,然后再对比是否反向和正向是一样的。这个方法的时间复杂度也为O(n³),效率较低。\[3\] 综上所述,方法一是解决LeetCode最长回文子串的最优解法。 #### 引用[.reference_title] - *1* [LeetCode_5_最长回文子串](https://blog.csdn.net/qq_38975553/article/details/109222153)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode-最长回文子串](https://blog.csdn.net/duffon_ze/article/details/86691293)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [LeetCode 第5最长回文子串(Python3解法)](https://blog.csdn.net/weixin_43490422/article/details/126479629)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值