Manacher's Algorithm

1.字串转换

1.1 转换 s ——> s’

转换的目的主要是避免奇偶讨论,两端的@和¥避免越界。
e.g.
1 2 3 2 1 -—-> @#1#2#3#2#1#¥
1 2 2 1 ——> @#1#2#2#1#¥

1.2 性质

定义:字串“1”半径为0,“#1#”半径为1,“#1#2#”半径为2
半径的定义不同,则性质表达式略有偏差
s 的长度 = s’ 的半径
s 的半径 = (s’ 的半径+1) / 2

2. 主算法思路

A. 定义一个大小与 s’ 长度相同的数组 r,作用第3步介绍。
B. 主循环:从左至右遍历 s’ 中各个字符(不包括两端),分别将 s’[i] 作为子串的中心点。
C. 循环内部:计算 r[i]。 r[i] 用于记录以 s’[i] 为中心点的最大回文子串的半径。
D. 得到 r 之后,无论是求最长回文子串还是回文子串总数等都很容易,不再详细展开。

3. 核心步骤

算法的核心步骤是计算r[i]

3.1 右端最靠右的最长回文子串 s_right

当进行完第 i 步迭代之后,s’[i]之前的每一个字符作为中心均对应有一个最长回文子串,其半径已经得出,分别为 r[0], r[1], …, r[i-1]。在这些最长回文子串中,我们在后面的计算中需要利用右端最靠右的那个最长回文子串,暂且命名为s_right。

于是定义两个变量 center, right,用于记录 s_right 的中心位置和最右端位置。每完成一步迭代之后(即计算完一个 r[i])进行一次更新。

3.2 分情况计算r[i]

3.2.1利用s_right对称性

A. 当 i < right
需要利用 s_right 的对称性。
不妨令 s’[i] 关于 center 对称的字符为 s’[j],其中 j = 2 * center - i。

a. 当 right - i >= r[j]
s’[j] 对应的最长回文子串完全在 s_right 内部。
在这里插入图片描述
根据 s_right 的对称性, r[i] >= r[j]
r[i] 的准确值还无法确定。
注:个人认为s’[i]对应的最长回文子串并不一定完全在 s_right 内部,很多博客上说 r[i] = r[j] 是不准确的,以s’[i]对应的最长回文子串右端完全可能越过right

b. 当 right - i < r[j]
s’[j] 对应的最长回文子串部分在 s_right 内部。
在这里插入图片描述
根据 s_right 的对称性, r[i] >= right - i。
r[i] 的准确值还无法确定。

B. 当 i >= right
无法利用 s_right 的对称性

3.2.2 常规扩展判断

利用完对称性后,还需要采用常规方法,向中心点两侧扩展,并进行判断。

4. C++程序

计算回文子串个数总和

class Solution {
public:
    int countSubstrings(string s) {    
        // s transfer to str
        string str = "@";
        for(auto a : s){
            str += '#';
            str += a;
        }
        str += "#$";
        
        // r[i] record the max radius when str[i] as the center
        vector<int> r(str.size(), 0);
        
        int center = 0, right = 0;  // 记录当前s_right的中心位置和最右端位置     
        // 以str[i](从i = 1至i = r.size() - 2)作为中心点,计算r[i]
        for(int i = 1; i <= r.size() - 2; ++i){
            // 利用对称性减少一部分两端比较的操作,核心步骤
            if(i < right){
                r[i] = min(right-i, r[2*center-i]);
            }
            // 常规两端比较
            while(str[i+r[i]+1] == str[i-r[i]-1]){
                ++r[i];
            }           
            // update center,right
            if(i+r[i] > right){
                center = i;
                right = i + r[i];
            }
        }
        
        // 计算回文子串之和
        int sum = 0;
        for(auto a : r){
            sum += (a + 1) / 2;
        }
        
        return sum;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值