LeetCode刷题记录——05最长回文字串

LeetCode刷题记录——05最长回文字串

一 题目描述

  • 给定一个字符串S,找到S的最长回文子串。你可以假设S的最大长度为100;

  • 示例:

    • 输入:“babad”
      输出:“bab”
      注意“aba”也是一个答案
          
      输入:“cbbd”
      输出:“bb”    
      

二 思路

  • 暴力破解:

    • 列举所有的字串,逐个判断是否为回文字串,保存最长的字串

    • bool IsPalindromic(string S){ //对字串进行判断
          int len = S.size();
          for(int i = 0;i<len/2;i++){
              if(S[i] != S[len-i-1]) //对应位置
                  return false;
          }
          return true;
      }
      
      string FindLongestPalindrome(string S){
          if(S == "")
              return "";
          string ans = "";
          int Max = 0;
          int len = S.size();
          for(int i = 0;i<len;i++){
              for(int j = i+1;j<len;j++){
                  string temp = S.substr(i,j-i+1);//subtr(i,length),从i开始,长度为length的串
                  if(IsPalindromic(temp) && temp.size()>Max){		//比较得到较长的串
                      ans = S.substr(i,j-i+1);
                     // Max = (Max > temp.size()) ? Max : temp.size();
                      Max = std::max(Max,int(temp.size()));
                  }
              }
          }
          return ans;
      }
      //时间复杂度为:O(n^3)
      //空间复杂度为:O(1)
      
      
  • 扩展中心,回文串一定是左右对称,循环选择一个中心,进行左右扩展,判断左右字符是否相等。

    • 扩展中心数量:我们可以从字符进行扩展(n),也可以从两个字符的中间进行扩展(n-1),因此一共有2n-1个中心

    • int expandAroundCenter(string s, int left, int right){
          int L = left, R = right;
      
          while(L >= 0 && R < s.length() && s[L] == s[R]){
              L--;
              R++;
          }
          return R-L-1;
      }
      
      string longestPalindrome(string s){
          if(s.empty())
              return "";
          int start = 0, end = 0;
          for(int i = 0;i<s.length();i++){
              int len1= expandAroundCenter(s,i,i); //以i为中心向左右散开
              int len2 = expandAroundCenter(s,i,i+1);//以i和i+1的中间为中心,左右散开
              int len = max(len1,len2);
              if(len > end -start){
                  start = i - (len-1)/2;
                  end = i + len/2;
              }
          }
          return s.substr(start,end-start+1);
      }
      //时间复杂度为O(n^2)
      //空间复杂度为O(1)
      
  • 马拉车算法(Manacher 算法)

    • 线性时间查找到到最长回文子串的方法

    • 我们需要将字符串先扩展为图中T的样子,即在首尾添加^和$,在中间添加#,于是就把任何长度的字符串变为了奇数长度

    • 然后用P来保存从中心C扩展的最大个数,并且刚好也是去掉#之后的原字符串的总长度,例如:下标是 6 的地方,可以看到 P[ 6 ] 等于 5,所以它是从左边扩展 5 个字符,相应的右边也是扩展 5 个字符,也就是 “#c#b#c#b#c#”,去掉#之后,变成 “cbcbc”,长度刚好是5
      在这里插入图片描述

    • 于是我们的关键就是要将P数组求出来,充分利用回文串的对成特性,

    • 在这里插入图片描述

    • C是对称中心,i_mirror是 i 关于C对称的位置,i_mirror = 2*C - i;R是回文串的右半径

    • 利用回文的对称性,P[i] = P[i_mirror],但是这不是在任何情况下都成立

    • 不成立的情况:

      • P[i_mirror] 超过了R:
        • 那么此时 i 的右边没有P[i_mirror]个元素,此时可以令P[i] = min( min(R-i,P[i_mirror])),就能保证,至于会不会更大,那么就需要使用扩展中心继续比较,看 P[i] 能否 ++ ;
        • P [ i_mirror ] 遇到了原字符串的左边界,即i_mirror 到了第一个字符的位置,此时需要用中心扩展法向两边扩展
        • i==R,此时将P[i]赋值为0,然后中心扩展
    • C和R的更新:当P[i]的右边界 > 当前的R时,就更新C和R为当前的回文串

    • string prepross(string s){
          int n = s.length();
          if(n == 0){
              return "^$";
          }
          string ret = "^";
          for(int i = 0;i<n;i++){
              (ret +="#") += s[i];
          }
          ret += "#$";
          return ret;
      }
      string longestPalindrome(string s){
          string T = prepross(s);
          int n = T.length();
          vector<int> P(n,0);
          int C = 0,R = 0;
          for(int i = 1;i<n-1;i++){
              int i_mirror = 2*C - i;
              if(R>i)
                  P[i] = min(R-i,P[i_mirror]); //防止超出R
              else
                  P[i] = 0;
      
              //中心扩展处理三种情况
              while(T[i+1+P[i]] == T[i-1-P[i]])
                  P[i]++;
              //判断是否需要更新R
              if(i+P[i] > R){
                  C = i;
                  R = i+P[i];
              }
          }
          //找出P的最大值
          int maxLen = 0;
          int centerIndex = 0;
          for(int i = 1;i<n-1;i++){
              if(P[i]>maxLen){
                  maxLen = P[i];
                  centerIndex = i;
              }
          }
          int start = (centerIndex - maxLen)/2;
          cout<<start<<"  ||  "<<maxLen<<endl;
          return s.substr(start,maxLen);
      }
      
  • 寻找两个串的最长公共子串

    • 回文串要求正着读和反着读,是一样的。因此是不是可以把原来的字符串反转,然后寻找这两个串的最长公共子串就可以了?试一下,例如:S = “caba” , S’ = “abac”,最长公共子串为”aba“,得解
    • 求最长公共子串,使用动态规划的方法

      • 申请一个二维数组 arr (保存公共子串的长度)并且全部初始化为0,然后判断对应的字符串是否相等,如果相等,就做:

        arr[i][j] = arr[i-1][j-1] + 1;
        
      • 这个方法具有局限性,对于某些字符串不能判断出来,很迷!!!

      • string LongestPalidrome(string s){
            if(s.empty())
                return "";
            string orgin = s;
            string reverse_s =s;
            reverse(reverse_s.begin(),reverse_s.end()); //将字符串倒置
            cout<<reverse_s<<endl;
            cout<<orgin<<endl;
            int length = s.size();
            vector<vector<int>> arr(length,vector<int>(length,0));
            int maxLen = 0;
            int maxEnd = 0;
            char temp1,tempj;
            for(int i=0;i<length;i++){
                for(int j = 0;j<length;j++){
                    temp1 =orgin[i];
                    tempj = reverse_s[j];
                    if(orgin[i] == reverse_s[j]){
                        if(i == 0 || j==0){
                            arr[i][i] = 1;
                        }
                        else{
                            arr[i][j] = arr[i-1][j-1] +1;
                        }
                    }
        
                    if(arr[i][j] > maxLen){
                        int beforeRev = length - 1 - j;
                        if(beforeRev + arr[i][j] - 1 == i){
                            maxLen = arr[i][j];
                            maxEnd = i;
                        }
                    }
                }
            }
            return s.substr(maxEnd-maxLen+1,maxLen+1);
        }
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值