LeetCode - Longest Palindromic Substring
来源:互联网 发布:药水哥网络臭要饭的 编辑:程序博客网 时间:2024/06/14 02:38
4. Longest Palindromic Substring
原题
Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest substring.
测试程序
我们先给出最长回文串的测试用例程序:
#include <iostream>#include <string>#include <vector>using namespace std;int main(){ string tests[] = { "", "a", "aa", "abc", "aba", "abba", "abad", "abeb", "$#aba#$cd", "ccccccccccccccccccccccccccccccccccccccccccccccccccccccc" }; string results[] = { "", "a", "aa", "a", "aba", "abba", "aba", "beb", "$#aba#$", "ccccccccccccccccccccccccccccccccccccccccccccccccccccccc" }; int total = sizeof(tests) / sizeof(tests[0]); cout <<total <<" tests" <<endl; for (int i = 0; i < total; ++i) { string palindrome = longest_palindromic_substring(tests[i]); if (results[i] == palindrome) { cout <<i <<": test ok" <<endl; } else { cout <<i <<": test failed" <<endl; } } return 0;}
备注:"abc"
中最长回文字符串可以是"a","b","c"
,测试程序中使用第一个"a"
。
朴素(暴力)算法
首先,最容易想到的就是遍历整个字符串S的所有子串,并判断其是否为回文子串,若是则判断其长度是否为最长的回文字符。字符串子串遍历的时间复杂度为
bool is_palindrome(char *s, int len){ char *e = s + len - 1; while (s < e) { if (*s != *e) { return false; } s++, e--; } return true; }string longest_palindromic_substring(string s){ int len = s.length(); if (len <= 1) { return s; } int max_len = 0; char *max_str = NULL; for (int i = 0; i < len; ++i) { for (int j = i; j < len; ++j) { if (is_palindrome(&s[i], j - i + 1)) { max_len = max_len >= j - i + 1 ? max_len : (max_str = &s[i], j - i + 1); } } } return string(max_str, max_len); }
中心扩展算法
上面的代码运行时间高达
- 遍历字符串,以当前字符作为中点,向左右两边扩展,当出现不相等的字符时,即回文截至。
- 在遍历过程中保存最大长度回文字符串起点及长度。
- 由于回文字符串的长度可能为奇数或偶数,因此需要针对奇偶长度不同分别判断。
从上面的步骤可以看到,改进后的方法仅需遍历一次字符串,时间复杂度为
string expand(string s, int l, int r){ int len = s.length(); while (l >= 0 && r < len && s[l] == s[r]) { l--, r++; } /* r - l - 1 means r - 1 - (l + 1) + 1 */ return s.substr(l + 1, r - l - 1);}string longest_palindromic_substring(string s){ int len = s.length(); string longest, tmp; for (int i = 0; i < len; ++i) { tmp = expand(s, i, i); if (longest.length() < tmp.length()) { longest = tmp; } tmp = expand(s, i, i + 1); if (longest.length() < tmp.length()) { longest = tmp; } } return longest;}
Manacher算法
接下来介绍经典的Manacher算法,该算法利用
预处理:在该阶段,它为每个字符两端插入一个特殊的字符,通常使用
'#'
字符。如下所示:primitive: “adcabacdef”
processed: “#a#d#c#a#b#a#c#d#e#f#”
primitive: “abbad”
processed: “#a#b#b#a#d#”因此,无论原始字符串长度是为奇数还是偶数,经预处理之后其长度均为奇数。这样就避免了考虑偶数的情况。由于在类C语言中字符串末尾包含
'\0'
字符,因此通常还会在字符串的开始加入一个特殊字符'$'
避免越界。计算回文半径: 回文半径即从回文中心点到回文最左边或最右边的距离。例如上面的示例中,我们可以计算每个字符的回文半径,如下所示:
primitive: “adcabacdef”
processed: # a # d # c # a # b # a # c # d # e # f #
radiuses[-]: 1 2 1 2 1 2 1 2 1 8 1 2 1 2 1 2 1 3 1 2 1上述中的空格是为了显示而加上的,rediuses数组中的
'-'
字符同样是为了显示效果而加上的。可以看出radiuses[i] - 1
既是原字符串中回文串的长度。这其实是可以证明的。证明:
- 首先有
L=2∗radiuses[i]−1 为新串中以processed[i]
为中心的回文串的长度。 - 以
processed[i]
为中心的回文串一定是以'#'
字符开始和结尾的,因此,当L
减去回文串最前(或最后)的'#'
字符后,其长度正好是原字符串的两倍,即(L−1)/2 ,将L 代入化解即得radiuses[i]−1 。
因此只要计算出预处理后字符串的回文半径数组就可以求出回文字符串了。Manacher算法在计算回文半径是利用到了动态规划的思路。它利用两个辅助变量
max_right
和max_index
来计算radiuses
数组。其中max_radius
代表预处理串中最大的回文字符串的最右边位置,max_index
则代表回文字符串的中心索引位置(该算法也可以只使用一个max_index
辅助变量)。步骤如下:i<max_right 意味着当前字符处于最大回文半径范围内,因此可以利用回文的特性找到其以max_index
为中心的对称点(max_index−(i−max_index)=2∗max_index−i )的回文半径,避免重复计算。- 若对称点的回文半径小于
max_right−i ,说明以字符processed[i]
为中的回文半径等于其对称点的回文半径。 - 若对称点的回文半径大于等于
max_right−i ,说明该点的回文半径至少为max_right - i
。此时还需要利用中心扩展法进行探测。
- 若对称点的回文半径小于
i>=max_right 意味着需要重新以中心扩展的方式计算回文半径。
- 首先有
Manacher算法该算法示例代码如下:
string preprocess(string s){ string result("$#"); int len = s.length(); for (int i = 0; i < len; ++i) { result.push_back(s[i]); result.push_back('#'); } return result;}string longest_palindromic_substring(string s){ string str = preprocess(s); int len = str.length(); vector<int> radiuses(len); int max_right = 0, max_index = 0; for (int i = 0; i < len; ++i) { /* core code */ if (max_right > i) { radiuses[i] = max_right - i < radiuses[2 * max_index - i] ? max_right - i : radiuses[2 * max_index - i]; } else { radiuses[i] = 1; } while (str[i - radiuses[i]] == str[i + radiuses[i]]) { radiuses[i]++; } if (max_right - max_index < radiuses[i]) { max_index = i; max_right = i + radiuses[i]; } } len = radiuses[max_index] - 1; int start = max_index - len; string result; while (len--) { result.push_back(str[start + 1]); /* skip '#' */ start += 2; } return result;}
一些博客说在每个字符两边插入的特殊字符不能在字符串中出现,其实我个人认为既是出现了也不会影响,关键在于重构回文字符串时如何判断。
参考
O(n) 回文子串(Manacher)算法- Manacher算法:求解最长回文字符串,时间复杂度为
O(N)
- LeetCode: Longest Palindromic Substring
- LeetCode Longest Palindromic Substring
- LeetCode: Longest Palindromic Substring
- [Leetcode] Longest Palindromic Substring
- [LeetCode] Longest Palindromic Substring
- Leetcode : Longest Palindromic Substring
- [LeetCode]Longest Palindromic Substring
- leetcode Longest Palindromic Substring
- LeetCode-Longest Palindromic Substring
- [LeetCode] Longest Palindromic Substring
- Longest Palindromic Substring leetcode
- LeetCode Longest Palindromic Substring
- LeetCode - Longest Palindromic Substring
- LeetCode -- Longest Palindromic Substring
- LeetCode | Longest Palindromic Substring
- LeetCode: Longest Palindromic Substring
- [LeetCode]Longest Palindromic Substring
- leetcode Longest Palindromic Substring
- OOP面向对象——封装
- HDU-5748 Bellovin 【LIS(STL应用)】
- 《VR入门系列教程》之5---应用方向
- Table 'performance_schema.session_variables' doesn't exist.
- 计算机网络--传输层
- LeetCode - Longest Palindromic Substring
- 最大公约数(gcd)辗转相除法
- tjut 4633
- L2-010. 排座位-PAT团体程序设计天梯赛GPLT(并查集)
- bzoj3531: [Sdoi2014]旅行
- 《VR入门系列教程》之6---VR硬件介绍及DK1
- 利用python smtplib 登录QQ邮箱发送邮件
- 安卓四大组件之service
- thinking in java 第一章对象导论总结