前言
本文介绍了 LeetCode 第 5 题 , “Longest Palindromic Substring”, 也就是 “最长回文子串” 的问题.
本文使用 C# 语言完成题目,介绍了 中心扩展法、马拉车算法 等 多种方法供大家参考。
题目
English
LeetCode 5. 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.
Example 1:
Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.
Example 2:
Input: “cbbd”
Output: “bb”
中文
LeetCode 5. 最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
解决方案
首先我们会想到使用 暴力法 来解决题目,用3层循环来对每个子串进行检查,最后取最长的子串作为结果,这样时间复杂度为 O(n^3) 。然后可能会考虑到使用动态规划的方式,以空间来换取时间,可以将时间复杂度优化为 O(n^2),但相应的空间复杂度会增大。
在仔细分析 回文串 的特点后,会想到使用 中心扩展法 ,该方法对于该题目来说不失为一种优秀的解决方法,时间复杂度为 O(n^2),空间复杂度为 O(1) ; 最后,还介绍了马拉车算法 Manacher ,这是一种非同寻常的算法,充分利用了 回文 的特点, 不可思议的将时间复杂度降为了 O(n) .
下面我们依次来介绍这几种方法。
方法一 : 暴力法
使用 3层循环 来依次对所有子串进行检查,将最长的子串最为最终结果返回。下面代码中,我们检查i到j的子串是否是回文串,如果是 且长度大于当前结果result的长度,就将result更新为i到j的子串。
public string LongestPalindrome(string s)
{
string result = "";
int n = s.Length;
for (int i = 0; i < n; i++)
{
for (int j = i; j < n; j++)
{
// 检查 s[i]到s[j]是否是回文串,如果是,且长度大于result长度,就更新它
int p = i, q = j;
bool isPalindromic = true;
while (p < q)
{
if (s[p++] != s[q--])
{
isPalindromic = false;
break;
}
}
if (isPalindromic)
{
int len = j - i + 1;
if (len > result.Length)
{
result = s.Substring(i, len);
}
}
}
}
return result;
}
执行结果
执行结果 超出时间限制。 虽然逻辑上没有问题,可以得到正确结果,但是执行时间过长。
复杂度分析
时间复杂度:O(n^3)
3层循环,所以是 O(n^3) .
空间复杂度:O(1)
仅使用了几个变量来存值,所以为 O(1) .
方法二 : 动态规划
方法一中,存在大量的重复计算工作,例如当 s=“abcba” 时, 对于子串 “bcb” 和 子串 “abcba”, 分别进行了2次完整的计算,来检测该子串是否是回文串。
很明显的是,对于 s=“abcba” , 在已知 "bcb"是回文串的情况下,要判断 "bcb"是否是回文串的话,只需要判断两边的*位置的字符是否相等即可。 我们定义 P(i,j) 表示 s[i,j]是否是回文串,若s[i,j]是回文串,则P(i,j)=true,否则为false. 则有下面的递推公式成立:
P[i,j] = p(i+1,j-1) && ( s[i]==s[j] )
对于上面公式有2个特殊情况,当子串长度为1或2时,上面公式不成立。我们单独分析这两种情况:
若子串长度为1,即 j==i, 则 P[i,j] = P[i,i] = true;
若子串长为2,即j