经典算法之 回文子串manacher algorithm

马拉车(manacher)算法是在求回文子串时,相对较快的一种算法
最普通的一种算法是,对给定的字符串string s, 将[0, s.length() - 1]分别作为中心,向两边延伸,直到两边的字符不相等,同时更新最大长度和中心点
由于上述算法还要考虑单数偶数的情况,因此可以在遍历前,手动给s添加分隔符'#', 比如123变为#1#2#3#,这样一来得到的字符串一定是奇数的,同样遍历一遍,得到最大长度和中心点,即可得到回文子串
但这样的时间复杂度为O(n^2),这时就又延伸出一个马拉车算法
网上已有很多代码与讲解,我在自认为理解之后写下我的理解:
首先是核心代码部分:

	int mx = 0, id = 0;
	int maxC = 0, maxL = 1;
	vector<int> p(ss.length(), 0);
	for (int i = 1; i < ss.length(); i++)
	{
		p[i] = (mx > i ? min(p[2 * id - i], mx - i) : 1);
		while (ss[i + p[i]] == ss[i - p[i]]) p[i]++;
		if (mx < i + p[i])
		{
			mx = i + p[i];
			id = i;
		}
		if (p[i] > maxl)
		{
			maxL = p[i];
			maxC = i - p[i];
		}
	}

而其中最为核心,也最需要理解的一行就是p[i] = (mx > i ? min(p[2 * id - i], mx - i) : 1);
这里用到的变量名都是延续网上用的比较多的
先说字符串ss,这是已经加了分隔符,同时为了防止越界,人为增加另一个特殊且一定不会在原字符串中出现字符的字符串,如123变为$#1#2#3#, 若不加$,只要在while循环中增加约束条件防止越界即可
数组pp[i]代表的是以ss中第i位为中心的回文字符串的半径
$#c#b#a#b#d#, 以a为中心的最大回文子串半径为4,以d为中心半径为2
这里的半径与原字符串的对应关系为,ss[i]的半径=s[i]的回文字串长-1
$#c#b#a#b#d#, 以a为中心的最大回文子串半径为4,对应到原字符串为bab,因此长为3=4-1
mx是目前以统计到的回文串能延伸到的最右端的位置,id则是该最右端位置对应的字符串中心
manacher1
如图所示,假设此时在求以i为中心的回文子串,已有p[id] + id = mx
如果imx右边, 说明目前还未遍历到i这个位置,此时就跟最前面介绍的算法一样,初始化半径为1,即只有ss[i]字符本身,之后暴力解即可,最后更新mx的位置
imx左边,此时又有两种情况:
首先看一个例子(没加分隔符):abcdhdcefecdhdcen
当遍历到i=8, 即s[i]='f'时,mx被更新为14,指向'c', p[8]=7
此时往后遍历,当遍历到i=12,s[i]='h',因为已经通过前面的遍历知道[2, 14]的字符串是回文字符串
那么以8为中心,12的对应位置4,p[4]=3, 如果p[4]+12 <= mx=14,那么可以直接认定p[12]=p[4]
而如果p[4]+12 > mx=14,那么只能确定p[12]至少有14-12=2这么长,后面的字符还没遍历到,则同样采用最前面的算法,往两边遍历直至跳出循环,并更新mx和id
上面的4对应j,8对应id,12对应i, 14对应mx, 即可对应到代码中
放一道leetcode例题 5. Longest Palindromic Substring 的答案

class Solution {
public:
    string longestPalindrome(string s)
    {
        string ss = "$#";
        for (int i = 0; i < s.length(); ++i)
        {
            ss += s[i];
            ss += '#';
        }
        int mx = 0, id = 0;
        // maxC记录中心,maxL记录长度
        int maxC = 0, maxL= 1;
        vector<int> p(ss.length(), 0);
        for (int i = 1; i < ss.length(); i++)
        {
            p[i] = (mx > i ? min(p[2 * id - i], mx - i) : 1);
            while (ss[i + p[i]] == ss[i - p[i]]) p[i]++;
            if (mx < i + p[i])
            {
                mx = i + p[i];
                id = i;
            }
            if (p[i] > maxL)
            {
                maxL= p[i];
                maxC= i;
            }
        }
        return s.substr((maxC-maxL) / 2, maxL- 1);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是使用QT实现Manacher算法的代码,同时包括了QT窗口的输入和输出。 首先,我们需要在QT Creator中创建一个新的Qt Widgets项目,然后在mainwindow.cpp文件中添加下面的代码: ``` #include "mainwindow.h" #include "ui_mainwindow.h" #include <QString> #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(findLongestPalindrome())); } MainWindow::~MainWindow() { delete ui; } void MainWindow::findLongestPalindrome() { QString str = ui->lineEdit->text(); QString result = longestPalindrome(str); ui->label->setText(result); } QString MainWindow::longestPalindrome(QString s) { QString t = "$#"; for (int i = 0; i < s.length(); ++i) { t += s.at(i); t += "#"; } int p[t.length()] = {0}; int mx = 0, id = 0, maxLength = 0, centerIndex = 0; for (int i = 1; i < t.length(); ++i) { p[i] = mx > i ? std::min(p[2 * id - i], mx - i) : 1; while (t[i + p[i]] == t[i - p[i]]) { ++p[i]; } if (mx < i + p[i]) { mx = i + p[i]; id = i; } if (maxLength < p[i]) { maxLength = p[i]; centerIndex = i; } } QString res; for (int i = centerIndex - maxLength + 1; i < centerIndex + maxLength; ++i) { if (t[i] != '#') { res += t[i]; } } return res; } ``` 在这段代码中,我们使用了QT的信号槽机制,将按钮的点击事件连接到了findLongestPalindrome()槽函数。该函数中,首先获取输入框中的字符串,然后调用longestPalindrome()函数来计算最长回文子串,最后将计算结果显示在label标签中。 longestPalindrome()函数实现了Manacher算法,对输入字符串进行预处理,并维护了变量p、mx、id、maxLength和centerIndex,最终返回最长回文子串。 下面是mainwindow.h文件的代码: ``` #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); QString longestPalindrome(QString s); public slots: void findLongestPalindrome(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H ``` 最后,在mainwindow.ui文件中添加下面的控件: - QLineEdit:用于输入字符串。 - QPushButton:用于触发计算最长回文子串的按钮。 - QLabel:用于显示计算结果。 然后,将这些控件与对应的槽函数和变量进行连接,就可以运行这个程序了。 注意:为了能够正常使用QString类和std::min函数,需要在mainwindow.cpp文件中添加下面两行代码: ``` #include <QString> #include <algorithm> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值