10.Manacher算法(用于解决回文子串问题)

Manacher算法

1.Manacher算法解决的问题

字符串str中,最长回文子串的长度如何求解?如何做到时间复杂度O(N)完成?

回文序列是从左往右和从右往左看一样,如abba,从左往右是abba,从右往左也是abba一样。

测试用例:abc12320de1,最长回文子串为232,长度为2.

1.1一般想到的解法

以每一个位置为对称轴向两边扩展,看看以每个位置为中心能扩多大。

例如:a121b11311c

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FuoBYvdc-1650859091024)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1650286307450.png)]

但是这个思路只能找到长度为奇数的回文,不能找到长度为偶数的回文。

例如:0110

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vSOjygm0-1650859091025)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1650286315475.png)]

按这种方法不能找到长度为偶数的回文0110。

1.2改善方法

可以在原始字符串左右两边和每两个字符串之间加上特殊字符。

例如:122131221

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QYz6gak7-1650859091025)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1650286325811.png)]

将扩完的回文长度除以2就能得到原串回文长度,这样不仅能找到奇数串回文长度也能找到偶数串回文长度。时间复杂度为O(N^2)。

2.Manacher算法

2.1回文半径、回文直径和回文半径数组

回文半径和回文直径的定义:

  • 回文直径:以一个字符为中心往两边括出来整个区域的大小叫回文直径。
  • 回文半径:以一个字符为中心往两边括出来一半区域的大小**(包括中心字符)**叫回文半径。

例如:#a#1#2#1#b#,以2为中心往两边扩回文直径为7,回文半径为4.

回文半径数组:从字符串左往右遍历,将每个字符的回文半径记录下来放进一个数组里就组成了一个回文半径数组。

如#a#1#2#1#b#的回文半径数组为:[1 2 1 2 1 4 1 2 1 2 1].

2.2之前扩的所有位置中所到达的最右回文右边界及中心点

名字比较拗口,举个例子:

例如:#1#2#2#1#...

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4XkZHMEX-1650859091025)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1650288139626.png)]

$$

R = max(R, 下标 + 回文半径 - 1)
​ ​
C = R ➗ 2
$$

2.3Manacher算法过程

①来到一个位置,不在最右回文边界里,暴力扩,没有优化。

②有优化:来到一个位置,在最右回文边界里,一定存在下述关系,i在中心点c和最右回文边界R中间,画出i关于c的对称点i’,根据i’的状况可以分为几个小类。

​ 1)i’ 的回文区域∈[L,R],此时i的回文半径等于i’的回文半径。(由于逆序关系)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yldcNnk6-1650859091026)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1650854718815.png)]

​ 2)i’的回文区域超出了L的左侧,此时i的回文半径等于R-i+1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tsl6SHvd-1650859091026)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1650854728829.png)]

​ 3)i’的回文区域左边界与L相同,则R’到R这一段一定是回文,但R’往左R往右的区域还需继续验证。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N7m5fcjY-1650859091026)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1650854908756.png)]

2.4代码

//将原始串变成处理串
string manacherString(string str) {
    string res;
    int index = 0;
    int size = str.size() * 2 + 1; //扩展后的字符串长度
    for (int i = 0; i != size; ++i) {
        res += (i % 2 == 0) ? '#' : str[index++];
    }
    return res;
}

//返回最长回文子串长度
int maxLcpsLength(string s) {
    if (s.size() == 0) {
        return 0;
    }
    string str = manacherString(s); //1221->#1#2#2#1#
    vector<int> pArr(str.size()); //回文半径数组
    int C = -1; //中心
    int R = -1; //回文右边界的右一个位置,最右的有效区域是R-1
    int maxValue = INT32_MIN;
    
    for (int i = 0; i != str.size(); ++i) { //求每个位置的回文半径
        //pArr[i]不用验证的区域
        //R > i表示i在R内,根据第二种情况的三个小情况优化;
        //否则i在R外,暴力扩,回文子串长度至少为1。
        pArr[i] = R > i ? min(pArr[2 * C - i], R - i) : 1;
        
        while (i + pArr[i] < str.size() && i - pArr[i] > -1) {
            if (str[i + pArr[i]] == str[i - pArr[i]]) {
                pArr[i]++;
            } else {
                break;
            }
        }
        
        if (i + pArr[i] > R) {
            R = i + pArr[i];
            C = i;
        }
        maxValue = max(maxValue, pArr[i]); 
    }
    return maxValue - 1;
}

3.例题

Manacher算法精髓在于其Manacher数组,不仅可以解决最长回文子序列问题,还能用于解决更多回文序列的问题。

【例题】:给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:s = “abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”
示例 2:

输入:s = “aaa”
输出:6
解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/palindromic-substrings

class Solution {
public:
        //将原始串变成处理串
    string manacherString(string str) {
        string res;
        int index = 0;
        int size = str.size() * 2 + 1; //扩展后的字符串长度
        for (int i = 0; i != size; ++i) {
            res += (i % 2 == 0) ? '#' : str[index++];
        }
        return res;
    }

    //返回最长回文子串长度
    vector<int> maxLcpsLength(string s) {
        if (s.size() == 0) {
            return {};
        }
        string str = manacherString(s); //1221->#1#2#2#1#
        vector<int> pArr(str.size()); //回文半径数组
        int C = -1; //中心
        int R = -1; //回文右边界的右一个位置,最右的有效区域是R-1
        
        for (int i = 0; i != str.size(); ++i) { //求每个位置的回文半径
            //pArr[i]不用验证的区域
            //R > i表示i在R内,根据第二种情况的三个小情况优化;
            //否则i在R外,暴力扩,回文子串长度至少为1。
            pArr[i] = R > i ? min(pArr[2 * C - i], R - i) : 1;
            
            while (i + pArr[i] < str.size() && i - pArr[i] > -1) {
                if (str[i + pArr[i]] == str[i - pArr[i]]) {
                    pArr[i]++;
                } else {
                    break;
                }
            }
            
            if (i + pArr[i] > R) {
                R = i + pArr[i];
                C = i;
            }
        }
        return pArr;
    }
    int countSubstrings(string s) {
        int res = 0;
        vector<int> pArr = maxLcpsLength(s);
        for (int i : pArr) {
            res += i / 2;
        }
        return res;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值