求最长回文子串(Longest Palindromic Substring)

     求解最长回文子串的问题最近经常遇到,特别是近期的笔试,因而有了一个学习的契机。先说说回文字符串,回文字符串的意思是从左往右看和从右往左看都是一样的,即我们如果以中心为轴,这个字符串是左右对称的,如字符串"abcba","abba"。字符串"abcba"有奇数个字符,所以以中间字符'c'为轴左右对称,而字符串"abba"有偶数个字符,所以是对半开来对称的。而顾名思义,最长回文子串就是指一个字符串中最长的具有回文性质的子串了。

    用暴力的方法是不现实的,一个字符串的子串个数很多(用2个for循环求解,i : 0-n-1,j : i- n-1),有n * (n+1) / 2个,如果对所有子串都进行回文判断,需要O(n3),显然耗时巨大。

    常规的求解方法有2种:动态规划法,中心扩展判断法,这2种算法都是O(n2)的时间复杂度。

  1. 动态规划法

      假设dp[ i ][ j ]的值为true,表示字符串s中下标从 i 到 j 的字符组成的子串是回文串。那么可以推出:

    dp[ i ][ j ] = dp[ i + 1][ j - 1] && s[ i ] == s[ j ]。

    这是一般的情况,由于需要依靠i+1, j -1,所以有可能 i + 1 = j -1, i +1 = (j - 1) -1,因此需要求出基准情况才能套用以上的公式:

    a. i + 1 = j -1,即回文长度为1时,dp[ i ][ i ] = true;

    b. i +1 = (j - 1) -1,即回文长度为2时,dp[ i ][ i + 1] = (s[ i ] == s[ i + 1])。

    有了以上分析就可以写出代码了。需要注意的是动态规划需要额外的O(n2)的空间。

    

//Written by zhou
//2013.11.22

string longestPalindrome(string s) {

        size_t n = s.length();
        bool **dp = new bool*[n];
        for (size_t i = 0; i < n; ++i)
        {
            dp[i] = new bool[n];
        }
                
        //为基准情况赋值
        int startPos = 0;
        int max = 1;
        for (size_t i = 0; i < n; ++i)
        {
            dp[i][i] = true;
            if (i + 1 < n)
            {
				if (s[i] == s[i+1])
				{
					dp[i][i+1] = true;
					startPos = i;
					max = 2;
				}
				else dp[i][i+1] = false;
            }
        }
        
        //动规求解,前面已求len = 1, len = 2的情况
        for (int len = 3; len <= n; ++len)
        {
            for (int i = 0; i < n - len + 1; ++i)
            {
                int j = i + len - 1;
                
				if (dp[i+1][j-1] && s[i] == s[j])
				{
					dp[i][j] = true;
					int curLen = j - i + 1;
					if (curLen > max)
					{
						startPos = i;
						max = curLen;
					}
				}
				else dp[i][j] = false;
                
            }
        }
        
		//释放二维数组
        for (size_t i = 0; i < n; ++i)
           delete[] dp[i];
        
        delete[] dp;        
		return s.substr(startPos,max);
    }

     

      2. 中心扩展法

      因为回文字符串是以中心轴对称的,所以如果我们从下标 i 出发,用2个指针向 i 的两边扩展判断是否相等,那么只需要对0到

n-1的下标都做此操作,就可以求出最长的回文子串。但需要注意的是,回文字符串有奇偶对称之分,即"abcba"与"abba"2种类型,

因此需要在代码编写时都做判断。

     设函数int Palindromic ( string &s, int i ,int j) 是求由下标 i 和 j 向两边扩展的回文串的长度,那么对0至n-1的下标,调用2次此函数:

     int lenOdd =  Palindromic( str, i, i ) 和 int lenEven = Palindromic (str , i , j ),即可求得以i 下标为奇回文和偶回文的子串长度。

     接下来以lenOdd和lenEven中的最大值与当前最大值max比较即可。

     这个方法有一个好处是时间复杂度为O(n2),且不需要使用额外的空间。

     代码如下,欢迎指导交流~

    

//Written by zhou
//2013.11.22

string longestPalindrome(string s) {
       
        size_t n = s.length();
        int startPos = 0;
        int max = 1;
        for (int i = 0; i < n; ++i)
        {
            int oddLen = 0, evenLen = 0, curLen;
            oddLen = Palindromic(s,i,i);
            
            if (i + 1 < n)
               evenLen = Palindromic(s,i,i+1);
            
            curLen = oddLen > evenLen? oddLen : evenLen;
            
            if (curLen > max)
            {
                max = curLen;
                if (max & 0x1)
                  startPos = i - max / 2;
                else 
                  startPos = i - (max - 1) / 2;
            }
        }
        
        return s.substr(startPos,max);
    }
    
    int Palindromic(const string &str, int i, int j)
    {
        size_t n = str.length();
        int curLen = 0;

        while (i >= 0 && j < n && str[i] == str[j])
        {
			--i;
            ++j;
        }
        curLen = (j-1) - (i+1) + 1;

        return curLen;
    }

 

     除了以上介绍的2种方法外,还有一种Manacher算法的O(n)的算法,待有时间学习时再做更新。

你可能感兴趣的:(数据结构&算法练习)