Leetcode005-Longest Palindromic Substring

来源:互联网 发布:手机升温软件 编辑:程序博客网 时间:2024/05/14 22:28

Leetcode005-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:

Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.

Example:

Input: “cbbd”
Output: “bb”

       寻找最长回文子字符串问题,是一个经典的动态规划问题。首先,我们应当写出其动态转移方程。明显的,假如一个字符串收尾字母相同,且中间部分为回文字符串,那么该字符串为回文字符串。
       record(i, j) ← ( record(i+1, j-1) and Si = Sj )
       利用一个n2空间的数组就可以存储回文字符串的状态。然后采用回溯的方式找到对应的最长子回文串并输出。使用两重循环,时间复杂度为O(n2)

AC版本

class Solution {public:    string longestPalindrome(string s) {        string res;        int length = s.size();        // 标记是否回文数        int record[length][length];        for(int i = 0; i<length; i++){            for(int j = 0; j<length; j++){                if ( j+i >= length) continue;                if (i < 2)                    s[j] == s[j+i] ? record[j][j+i] = 1 : record[j][j+i] = 0;                else if(s[j] == s[j+i])                    record[j][j+i] = record[j+1][j+i-1];                else                    record[j][j+i] = 0;            }        }        // 回溯        for(int i = length-1; i>=0; i--){            for(int j = 0; j<length; j++){                if ( j+i >= length ) continue;                if (record[j][j+i]){                    res = s.substr(j, i+1);                    return res;                }            }        }    }};

       这个实现虽然通过了但是由于代码结构的问题,时间效率不高,调整后的代码结构如下:

class Solution {public:    string longestPalindrome(string s) {        string res;        // 标记最长子回文串初始位置        int maxbegin = 0;        // 标记最长子回文串长度        int maxlen = 1;        int length = s.size();        // 标记是否回文数        bool record[length][length];        for(int i = 0; i<length; i++){            record[i][i] = true;            if(i+1 < length){                if(s[i]==s[i+1]){                    record[i][i+1] = true;                    maxlen = 2;                    maxbegin = i;                }                else                    record[i][i+1] = false;            }        }        for(int sublen = 3; sublen<=length; sublen++){            for(int i = 0; i<length-sublen+1; i++){                int j = i + sublen - 1;                if(s[i] == s[j] && record[i+1][j-1]){                    record[i][j] = true;                    maxlen = sublen;                    maxbegin = i;                }                else                    record[i][j] = false;            }        }        res = s.substr(maxbegin, maxlen);        return res;    }};


O(n)时间的算法

       查阅相关资料,发现了一个时间复杂度的方法O(n),建议大家去这里查看原版的解答。本文剩余部分是对其的一些中文说明。

参数说明:

Name Represent center 当前回文串中心位置 r 当前回文串右侧端点位置 maxlen 当前最长回文子串的长度 maxcenter 当前最长回文子串的中心位置 sublen[] 对应字符为中心的回文串长度


算法的主要步骤:

  1. 预处理,在字符串每一字符前后添加“#”进行分隔;
  2. 遍历字符串;
  3. 计算以当前元素为中心的回文子串的大小,存于sublen[]中;
  4. 回文子串右端点>center为中心子串的右端点r,更新center和r。

算法举例: 字符串“abaca”,预处理为“#a#b#a#c#a#”。
这里写图片描述

如何计算以i为中心的子回文串长度:

       这个部分算是这个算法的重点了,算法利用回文串的对称性,简化了计算子回文串长度的步骤,具体做法如下:

  1. 假设以i为中心的子回文串右端不超过以c为中心的子回文串右端
    • 利用对称性,计算“a”( i=9)为中心的子串时,其子串长度与对称位置的“a”(i=5)的长度类似,此处相等,均为1;
    • 也存在特殊,计算“b”( i=11)为中心的子串时,其子串长度本应与对称位置的“b”(i=3)的长度类似,但i=3处对应的回文串“aba”范围超出了以“c”为中心(图中橘色部分)的子串的范围,所以此处“b”的子串右端点应于“c”的子串右端点相同。(特意注明:此处i=13位置字符一定与i=1处不同,否则以“c”为中心的回文子串会更长)
      这里写图片描述
  2. 假设以i为中心的子回文串右端超过以c为中心的子回文串右端
    • 利用对称性,计算“b”( i=11)为中心的子串时,其子串长度至少应与对称位置的“b”(i=3)的长度相等,但i=11处对应的回文串“aba”范围超出了以“c”为中心(图中橘色部分)的子串的范围,所以需要对其进行扩展。(特意注明,扩展之后,要更新center和r)
      这里写图片描述

算法的时间复杂度:

       从理论上解析,使用了两层循环(一层遍历,一层扩展)应当是O(n2)复杂度的,但实际上,扩展的越多表示对称覆盖的越广,这样导致需要扩展的元素越少。是一个相互制约的过程。这个算法的时间复杂度,需要使用平摊分析(amortized analysis)的方法进行分析。具体的我本人也没有分析过,(有时间的朋友可以分析一下,再留言。)在此略过。

AC版本

/** O(n)版本 */string preprocess(string s){    int n = s.size();    string res = "#";    for(int i=0; i<n; i++){        res += s[i];        res += "#";    }    return res;}bool isvalid(string s, int n){    return n>=0 && n<s.size();}class Solution {public:    string longestPalindrome(string s) {        string str = preprocess(s);        int n = str.size();        vector<int> sublen;        int center = 0;        int r = 0;        int maxcenter = 0;        int maxlen = 0;        for(int i=0; i<n; i++){   // 按i为center遍历            if(r <= i)                sublen.push_back(0);            int sym_i = 2*center-i; // sym_i - center = center - i            if(r > i)                sublen.push_back( min(r-i, sublen[sym_i]) );            // 扩展i为中心的子回文串的右端点            while(true){                if(!isvalid(str, i+sublen[i]+1) || !isvalid(str, i-sublen[i]-1))                    break;                if(str[i+sublen[i]+1] == str[i-sublen[i]-1])                    sublen[i]++;                else                    break;            }            // i为中心的子回文串右端点在标记r的右侧,更新center和r            if(i+sublen[i] > r){                center = i;                r = i + sublen[i];            }            if(sublen[i]>maxlen){                maxlen = sublen[i];                maxcenter = i;            }            // cout << sublen[i] << " ";        }        return s.substr((maxcenter+1-maxlen)/2,maxlen);    }};
0 0