zoukankan      html  css  js  c++  java
  • 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 };

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

  • 相关阅读:
    cookie和session。
    K3cloud Web API对接---单据保存接口(有源单)
    K3 wise kis 防火墙设置
    新单序时簿插件
    mssqlserver中排序规则冲突的问题解决
    读取金蝶图片
    金蝶wise委外订单关闭简述
    存储过程加锁
    判断存储过程是否存在
    解除死锁
  • 原文地址:https://www.cnblogs.com/xiaozhuyang/p/6242325.html
Copyright © 2011-2022 走看看