L2-008 最长对称子串(动态规划+中心扩展+马拉车算法)

对给定的字符串,本题要求你输出最长对称子串的长度。例如,给定Is PAT&TAP symmetric?,最长对称子串为s PAT&TAP s,于是你应该输出11。

输入格式:

输入在一行中给出长度不超过1000的非空字符串。

输出格式:

在一行中输出最长对称子串的长度。

输入样例:

Is PAT&TAP symmetric?

输出样例:

11

一、暴力枚举 (直接三层循环就可以)o(n^3)

#include <iostream>
#include <string>
using namespace std;

// 检查子串 s[left...right] 是否为回文
bool isPalindrome(const string& s, int left, int right) {
    while (left < right) {
        if (s[left] != s[right]) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}

int longestSymmetricSubstring(const string& s) {
    int n = s.length();
    if (n == 0) return 0;

    int maxLen = 1; // 至少一个字符是对称的

    // 枚举所有子串
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            // 检查子串 s[i...j] 是否为回文
            if (isPalindrome(s, i, j)) {
                int currentLen = j - i + 1;
                if (currentLen > maxLen) {
                    maxLen = currentLen;
                }
            }
        }
    }

    return maxLen;
}

int main() {
    string s;
    getline(cin, s);
    cout << longestSymmetricSubstring(s) << endl;
    return 0;
}

二、动态规划

  1. 定义状态dp[i][j] 表示字符串从第 i 个字符到第 j 个字符是否为对称子串。

  2. 状态转移方程

    • 如果 s[i] == s[j] 并且 dp[i+1][j-1] 为 true,那么 dp[i][j] 也为 true

    • 如果 i == j,那么 dp[i][j] 为 true(单个字符是对称的)。

    • 如果 j == i + 1 并且 s[i] == s[j],那么 dp[i][j] 为 true(两个相同字符是对称的)。

  3. 初始化:所有单个字符的子串都是对称的,即 dp[i][i] = true

  4. 结果:在填充 dp 表的过程中,记录最长的对称子串的长度。

#include<bits/stdc++.h>
using namespace std;
void solve(){
    string s;
    getline(cin,s);
    int n=s.length();
    if(n==0)cout<<0<<endl;
    else{
        vector<vector<bool>>dp(n,vector<bool>(n,false));
        int maxlen=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;
                maxlen=2;
            }
        }
        for(int length=3;length<=n;length++){
            for(int i=0;i<n-length+1;i++){
                int j=i+length-1;
                if(s[i]==s[j]&&dp[i+1][j-1])
                {
                    dp[i][j]=true;
                    maxlen=max(maxlen,length);
                }            
            }
        }
        cout<<maxlen<<endl;
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

三、中心扩展法

 

  1. 对称子串的中心

    • 对称子串可以是奇数长度(中心是一个字符),例如 aba

    • 对称子串也可以是偶数长度(中心是两个字符),例如 abba

  2. 遍历所有可能的中心

    • 对于每个字符,作为奇数长度对称子串的中心,向左右扩展。

    • 对于每两个相邻字符,作为偶数长度对称子串的中心,向左右扩展。

  3. 记录最大长度:在扩展过程中,记录最长的对称子串的长度。

#include<bits/stdc++.h>
using namespace std;
int expand(string s,int left,int right){
    while(left>=0&&right<s.length()&&s[left]==s[right]){
        left--;
        right++;
    }
    return right-left-1;
}
void solve(){
    string s;
    getline(cin,s);
    int n=s.length();
    if(n==0)cout<<0<<endl;
    else{
        int maxlen=1;
        for(int i=0;i<n;i++){
            int len1=expand(s,i,i);//奇数长度的对称子串
            int len2=expand(s,i,i+1);//偶数长度的对称子串
            int currentmax=max(len1,len2);
            maxlen=max(maxlen,currentmax);
        }
        cout<<maxlen<<endl;
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    solve();
    return 0;
}

四、马拉车算法(利用回文串的对称性,避免了重复计算)

 

  1. 预处理

    • 将原始字符串插入特殊字符(如 #),使得所有回文子串都变为奇数长度。例如,aba 变成 #a#b#a#abba 变成 #a#b#b#a#

    • 这样,无论是奇数长度还是偶数长度的回文子串,都可以统一处理。

  2. 利用对称性

    • 维护一个数组 p,其中 p[i] 表示以字符 s[i] 为中心的最长回文半径(包括中心字符)。

    • 维护两个变量:

      • C:当前已知的最长回文子串的中心。

      • R:当前已知的最长回文子串的右边界。

    • 对于每个字符 s[i],利用对称性快速计算 p[i]

  3. 快速计算 p[i]

    • 如果 i 在 R 的左侧,则 p[i] 的初始值可以通过对称性确定为 min(R - i, p[2 * C - i])

    • 如果 i 在 R 的右侧,则从 p[i] = 1 开始扩展。

    • 扩展 p[i] 直到字符不匹配或超出边界。

  4. 更新 C 和 R

    • 如果以 i 为中心的回文子串的右边界超过了 R,则更新 C = i 和 R = i + p[i]

  5. 结果

    • 最终,p 数组中的最大值即为最长回文子串的长度。

#include<bits/stdc++.h>
using namespace std;
void solve(){
    string s;
    getline(cin,s);
    int maxlen=0;
    string t="#";
    for(char ss:s){
        t+=ss;
        t+='#';
    }
    int n=t.length();
    vector<int>p(n);
    int c=0;
    int r=0;
    for(int i=0;i<n;i++){
        if(i<r){
            p[i]=min(r-i,2*c-i);
        }
        while(i+p[i]+1<n&&i-p[i]-1>=0&&t[i+p[i]+1]==t[i-p[i]-1]){
            p[i]++;
        }
        if(i+p[i]>r){
            r=i+p[i];
            c=i;
        }
        maxlen=max(maxlen,p[i]);
    }
    cout<<maxlen<<endl;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    solve();
    return 0;
}
if (i < R) {
    p[i] = min(R - i, p[2 * C - i]);
}
  • i < R:如果当前字符 t[i] 在当前最长回文子串的右边界 R 的左侧,则可以利用对称性快速初始化 p[i]

  • 2 * C - i:这是字符 t[i] 关于中心 C 的对称点 j

  • p[j]:以字符 t[j] 为中心的最长回文半径。

  • R - i:当前字符 t[i] 到右边界 R 的距离。

  • min(R - i, p[j]):取 R - i 和 p[j] 的较小值,作为 p[i] 的初始值。


为什么取较小值?

  • 如果 p[j] 较小,说明以 t[j] 为中心的回文子串完全包含在当前最长回文子串内,因此 p[i] 可以直接等于 p[j]

  • 如果 p[j] 较大,说明以 t[j] 为中心的回文子串可能超出了当前最长回文子串的范围,因此 p[i] 只能初始化为 R - i,因为超出 R 的部分还未检查。


示例说明

假设预处理后的字符串为 t = "#a#b#a#",当前最长回文子串的中心 C = 3,右边界 R = 5

  • 对于 i = 4

    • 对称点 j = 2 * C - i = 2 * 3 - 4 = 2

    • p[j] = p[2] = 1(以 t[2] = 'b' 为中心的最长回文半径)。

    • R - i = 5 - 4 = 1

    • 因此,p[i] = min(1, 1) = 1

  • 对于 i = 5

    • 对称点 j = 2 * C - i = 2 * 3 - 5 = 1

    • p[j] = p[1] = 1(以 t[1] = 'a' 为中心的最长回文半径)。

    • R - i = 5 - 5 = 0

    • 因此,p[i] = min(0, 1) = 0

 

方法时间复杂度空间复杂度适用场景
暴力枚举法O(n³)O(1)简单实现,适合小规模字符串
动态规划O(n²)O(n²)需要记录所有子串是否为回文
中心扩展法O(n²)O(1)简单实现,空间复杂度低
马拉车算法O(n)O(n)最优解,适合大规模字符串
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值