【蓝桥杯每日一题】3.8 最长回文子序列

问题描述

如果一个字符串正着读和倒着读是一样的,则称它是回文的。

给定一个长度为 N N N 的字符串 S S S,求字符串 S S S最长回文子串的长度是多少

样例1:

输入:

abcbabcbabcba

输出:

13
样例:2:

输入:

abacacbaaaab

输出:

6
解法一:暴力求解

时间复杂度: O ( n 3 ) O(n^3) O(n3)

截取字符串的所有子串,然后再判断这些子串中哪些是回文的,最后返回回文子串中最长的即可。

使用两个变量,一个记录最长回文子串开始的位置,一个记录最长回文子串的长度,最后再截取。

截取所有子串,如果截取的子串小于等于之前遍历过的最大回文串,直接跳过。因为截取的子串即使是回文串也不可能是最大的,所以并不需要判断

//暴力求解
#include <bits/stdc++.h>
using namespace std;

bool isPalindrome(string s, int start, int end) {
    while (start < end) {
        if (s[start] != s[end])
            return false;
        start++;
        end--;
    }
    return true;
}

int main() 
{
    string s;
    cin >> s;
    
    int maxLength = 0;
    int n = s.length();
    
    for (int i = 0; i < n; i++)
	{
        for (int j = i; j < n; j++) 
		{
            if (isPalindrome(s, i, j)) 
			{
                int length = j - i + 1;
                if (length > maxLength)
                    maxLength = length;
            }
        }
    }
    
    printf("%d",maxLength);
    return 0;
}
解法二:中心拓展法

时间复杂度: O ( n 2 ) O(n^2) O(n2)

中心拓展法的思路是以每个字符为中心,向左右扩展,寻找回文子序列的长度

实现时,我们使用两个指针,分别指向当前字符或相邻两个字符,然后向两侧扩展。

以每个字符或相邻两个字符为中心,分别进行奇数和偶数长度的回文子序列的寻找。对于每个回文子序列,我们更新最长回文子序列的长度和起始位置。返回找到的最长回文子序列。

注意点:

  • 回文串的长度不一定都是奇数,也可能是偶数
  • 以每个字符或相邻两个字符为中心,分别进行奇数和偶数长度的回文子序列的寻找

代码:

#include <bits/stdc++.h>
using namespace std;

int expandAroundCenter(string s, int left, int right) {
    while (left >= 0 && right < s.length() && s[left] == s[right]) {
        left--;
        right++;
    }
    return right - left - 1;
}

int main() 
{
    string s ;
    cin>>s;

    int start = 0, end = 0;
    for (int i = 0; i < s.length(); i++) 
	{
        int len1 = expandAroundCenter(s, i, i);
        int len2 = expandAroundCenter(s, i, i + 1);
        int len = max(len1, len2);
        if (len > end - start) 
		{
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    printf("%d",end - start + 1);
    return 0;
}
解法三:线性DP(动态规划)

时间复杂度: O ( n 2 ) O(n^2) O(n2)

思路:利用已经计算过的子问题的结果来求解更大规模的问题

#include <bits/stdc++.h>
using namespace std;

int main() 
{
    string s;
    cin >> s;

	int n = s.length();
    vector<vector<bool>> dp(n, vector<bool>(n, false));
    int maxLength = 1;  // 最小回文子串长度为1
    
    // 所有长度为1的子串都是回文子串
    for (int i = 0; i < n; i++) {
        dp[i][i] = true;
    }
    
    // 检查长度为2的子串
    for (int i = 0; i < n - 1; i++) {
        if (s[i] == s[i + 1]) {
            dp[i][i + 1] = true;
            maxLength = 2;
        }
    }
    
    // 检查长度大于2的子串
    for (int len = 3; len <= n; len++) {
        for (int i = 0; i <= n - len; i++) {
            int j = i + len - 1;
            if (s[i] == s[j] && dp[i + 1][j - 1]) {
                dp[i][j] = true;
                maxLength = max(maxLength, len);
            }
        }
    }
    
    printf("%d",maxLength);
    return 0;
}
解法四:马拉车算法(Manacher)

详情可见:

【马拉车算法 | Coding Club】

时间复杂度: O ( n ) O(n) O(n)
代码:

#include <bits/stdc++.h>
using namespace std;
const int N=22000010;
char s[N],str[N];
int p[N];   //用于存放每个字符可以中心扩展几次

int init()              //处理原字符串
{
        int len=strlen(s); 
        str[0]='#'; str[1]='$';         //@是防止越界
        int j=2;
        for ( int i=0; i<len; i++ )
                str[j++]=s[i],str[j++]='$';
        str[j]='\0'; 
		return j;
}

int manacher()
{
        int ans=-1,len=init(),r=0,c=0;
        for ( int i=1; i<len; i++ )
        {
            if ( i<= r ) p[i]=min( p[c*2-i],r-i );             //situation1
            else p[i]=1;                                  //situation2
            while ( str[i+p[i]]==str[i-p[i]] ) p[i]++;                //扩展  
            if ( p[i]+i> r ) 
			{
				r=p[i]+i;
				c=i;          //更新中心位置
			}
				          
            ans=max(ans,p[i]-1 );
        }
        return ans;
}

int main()
{
        int cnt=0;
        while ( scanf( "%s",s) && s[0]!='E' ) 
                printf( "Case %d: %d\n",++cnt,manacher() );
}

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值