LeetCode5. Longest Palindromic Substring 最长回文子串 4种方法

题目链接:https://leetcode.com/problems/longest-palindromic-substring/

题意很简单,就是求一个字符串得最长子串,这里的子串指连续的。

本文给出四个不同时间的解法。在LeetCode上的用时分别是500ms,250ms,60ms以及6ms。

(1)500ms-最朴素解法

这种解法相当于模拟求解了,是一种正向思维,即枚举所有起点和终点,判断长度是否最长,且是回文的。时间复杂度O(n3).代码如下:

 1 class Solution {
 2 public:
 3     string longestPalindrome(string s) {
 4         int maxlen = 0;
 5         int len = s.length();
 6         string ans;
 7         int r = 0,l = 0;
 8         
 9         for(int i = 0; i < len; i++)
10         {
11             for(int j = len - 1; j > i; j--)
12             {
13                 if (j - i + 1 <= maxlen) break;
14                 bool flag = 0;
15                 for(int k = 0; k < (j - i + 1) / 2; k++)
16                 {
17                     if(s[i + k] != s[j - k])
18                     {
19                         flag = 1;
20                         break;
21                     }
22                 }
23                 if(flag == 0)
24                 {
25                     maxlen = j - i + 1;
26                     l = i;
27                     r = j;
28                 }
29             }
30         }
31         return s.substr(l,r - l + 1);
32     }
33 };

(2)250ms----DP

以空间换时间,定义一个布尔类型的数组p[][],负责存储以 i、j 为左右界的子串是否为回文的。然后再遍历一遍,寻找是回文且最长的。时间复杂度O(n2)。

在求解 p 数组时,采用的策略是不断扩大 p 的长度,易知:

p[i][i] = true;

p[i][i+1] = (s[i] ==  s[i+1]);

那么,当长度为 k+1 时,有

p[i][i+k] = (p[i + 1][i + k - 1]) && (s[i] == s[i+k]);

从上式可以看出,要求长度为 k+1 时的回文与否,就得知道长度为 k 时的回文与否。因而代码如下:

 1 class Solution {
 2 public:
 3     string longestPalindrome(string s) {
 4         int maxlen = 0;
 5         int len = s.length();
 6         int r = 0,l = 0;
 7         bool p[1005][1005];
 8         for(int i = 0; i < len; i ++)
 9         {
10             p[i][i] = true;
11             p[i][i+1] = s[i] == s[i+1];
12         } 
13         for(int k = 2; k < len; k++)
14         {
15             for(int i = 0; i < len && i + k < len; i++)
16             {
17                 p[i][i+k] = s[i] == s[i+k] && p[i+1][i+k-1];
18             }
19         }
20         for(int i = 0; i < len; i++)
21             for(int j = 0; j < len; j ++)
22             if(p[i][j] && j - i + 1 > maxlen)
23             {
24                 l = i;
25                 r = j;
26                 maxlen = j - i + 1;
27             }
28         
29         return s.substr(l,r - l + 1);
30     }
31 };

(3)60ms---中心扩展法

前面两种都是正向思维,后面这两种是以解为起点,逆向构造回文子串。解法(3)最坏时间复杂度是O(n2),虽然时间复杂度与解法(2)是一样的,但是逆向求解很大程度上减少了遍历的次数,达不到回文的基本条件就不会继续扩展,在很大程度上降低了时间代价。

这种解法也可叫做从中间扩散(ExpandAroundCenter)。奇数子串:以 i 为中心,向外扩展;偶数子串:以 (i,i+1)为中心,向外扩展。扩展时满足:左右界不超出字符串边界且对称相等。以上述方法算出以 i 为中心的奇/偶数子串中长的一个作为当前的回文子串长度temp,然后比较temp与maxlen,重复该步骤求出最终解。

其中,确定了temp后,如何获得该子串的左右界需要考虑一下,举个实例就算出来了:l = i - (temp - 1) / 2, r = temp - 1 + l; 其实 l 算出来了,根据 temp = r - l + 1 就可以求出 r 了。

代码如下:

 1 class Solution {
 2 public:
 3     string longestPalindrome(string s) {
 4         int maxlen = 0;
 5         int len = s.length();
 6         int r = 0,l = 0;
 7         for(int i = 0; i < len; i ++)
 8         {
 9             int temp = max(expandAroundCenter(s,i,i),expandAroundCenter(s,i,i+1));
10             if(temp > maxlen)
11             {
12                 l = i - (temp - 1) / 2;
13                 r = temp - 1 + l;
14                 maxlen = temp;
15             }
16         }
17         return s.substr(l,r - l + 1);
18     }
19 private:
20     int expandAroundCenter(string s, int l, int r)
21     {
22         while(l >= 0 && r < s.length() && s[l] == s[r])
23         {
24             l--;
25             r++;
26         }
27         return r - l - 1;
28     }
29 };

(4)6ms---改进的中心扩展法

由解法(1)到解法(3)的过程中可以看出,缩短时间的思想是不断减少不必要的计算或重复的计算。解法(4)就是在解法(3)的基础上进一步缩减重复的计算。缩减的策略是,不是以一个字符 i 为中心计算其奇数或偶数长度的回文子串,而是以一个由相同字符组成的连续子串为中心向外扩展。仔细想想其实是很有道理的。字符个数总共只有128个,当字符串长度很大时,构成回文子串的字符中最有可能是重复的字符反复出现。从最终的运行时间可能看出这种策略的优势,比解法(3)平均缩短了10倍的时间。

代码如下:

 1 class Solution {
 2 public:
 3     std::string longestPalindrome(std::string s) {
 4         if (s.size() < 2)
 5             return s;
 6         int len = s.size(), max_left = 0, max_len = 1, left, right;
 7         for (int start = 0; start < len && len - start > max_len / 2;) {
 8             left = right = start;
 9             while (right < len - 1 && s[right + 1] == s[right])//求出重复字符组成的子串的右界
10                 ++right;
11             start = right + 1;//下一轮遍历的起点。
12             while (right < len - 1 && left > 0 && s[right + 1] == s[left - 1]) {//不断向外扩展
13                 ++right;
14                 --left;
15             }
16             if (max_len < right - left + 1) {
17                 max_left = left;
18                 max_len = right - left + 1;
19             }
20         }
21         return s.substr(max_left, max_len);
22     }
23 };

 

总结:主动寻找构造解的方式 比 被动搜索解的方式 效率更高!

posted on 2017-01-02 13:11  GyyZyp  阅读(191)  评论(0编辑  收藏  举报

导航