[LeetCode] 5. Longest Palindromic Substring 最长回文子串

     本题求给定字符串的最长回文子串,首先可以想到使用暴力的方法,求出给定字符串的所有的回文子串的长度,取长度最长的子串,具体地分回文子串长度为奇数和长度为偶数讨论,时间复杂度O(n^2),但此暴力求解的方法在leetcode上会报超时错误,具体代码如下:

一. 暴力法

参考代码

#include<string>
#include<string.h>
using namespace std;
class Solution {
public:
    string longestPalindrome(string s) {
       if(s.empty()||s.size()<=1) return s;
       int size = s.size();
       string odd_str = longestPalindrome_odd(s,size);
       string even_str = longestPalindrome_even(s,size); 
       return odd_str.size() >= even_str.size()?odd_str:even_str;
    }
    
    string longestPalindrome_odd(string& s,int size)
    {
       string res;
       for(int i = 0; i<size; ++i){
           string substr =  s.substr(i,1);
           for(int l = i-1,r = i+1;l>=0 && r<=size-1;l--,r++){
                if(s[l]!=s[r]) break;
                 else{
                   substr = s.substr(l,r-l+1);
                }
           }
           if(substr.size()>=res.size()) res = substr;
       }
       return res;
    }

    string longestPalindrome_even(string& s,int size)
    {
        string res; 
        for(int i = 0; i<size; ++i){
            string substr;
            for(int l = i,r = i+1;l>=0&&r<=size-1;l--,r++){
                if(s[l]!=s[r]) break;
                else{
                  substr = s.substr(l,r-l+1);
                }
            }
            if(substr.size()>=res.size()) res = substr;
        }
        return res;
    }
};

 

二  动态规划:

第 1 步:定义状态
    dp[i][j] 表示子串 s[i, j] 是否为回文子串。定义状态先尝试“题目问什么,就把什么设置为状态”。然后考虑“状态如何转移”,如果“状态转移方程”不容易得到,尝试修改状态定义,目的仍然是为了方便得到“状态转移方程”。

 

第 2 步:思考状态转移方程

   这一步在做分类讨论(根据头尾字符是否相等),根据对状态定义的分析得到:

dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
分析这个状态转移方程:

(1)“动态规划”事实上是在填一张二维表格,i 和 j 的关系是 i <= j ,因此,只需要填这张表的上半部分;

(2)在 s[i] == s[j] 成立和 j - i < 3 的前提下,直接可以下结论,dp[i][j] = true,否则才执行状态转移。

  注:分类讨论是构造转态转移方程的技巧。对状态空间进行分类,思考最优子结构到底是什么。即大问题的最优解如何由小问题的最优解得到。

 

第 3 步:考虑初始化

初始化的时候,单个字符一定是回文串,因此把对角线先初始化为 1,即 dp[i][i] = 1 。

事实上,初始化的部分都可以省去。因为只有一个字符的时候一定是回文,dp[i][i] 根本不会被其它状态值所参考。

第 4 步:考虑输出
只要一得到 dp[i][j] = true,就记录子串的长度和起始位置,没有必要截取,因为截取字符串也要消耗性能,记录此时的回文子串的“起始位置”和“回文长度”即可。

第 5 步:考虑状态是否可以压缩
因为在填表的过程中,只参考了左下方的数值。事实上可以压缩,但会增加一些判断语句,增加代码编写和理解的难度,丢失可读性。在这里不做状态压缩。

下面是编码的时候要注意的事项:总是先得到小子串的回文判定,然后大子串才能参考小子串的判断结果。

思路是:

1、在子串右边界 j 逐渐扩大的过程中,枚举左边界可能出现的位置;

2、左边界枚举的时候可以从小到大,也可以从大到小。

参考代码2 :

class Solution {
public:
    string longestPalindrome(string s) {
        const int size = s.size();
        if(s.empty()||size <= 1)
           return s;
        int l=0,h=0,seq=0;
       bool dp[size][size];
        for(int j = 1;j<size;++j)
           for(int i = 0;i<=j;++i)
            {
                int sub_seq = j-i+1;
                if(sub_seq<3)
                {
                    dp[i][j]=s[i]==s[j];
                }
                else
                {
                     dp[i][j]=(s[i]==s[j]&&dp[i+1][j-1]);
                }
                if(dp[i][j]&&sub_seq>=seq){
                    l=i;
                    h=j;
                    seq=sub_seq;
                }
            }
            return s.substr(l,seq);
    }
};
posted @ 2020-01-06 17:41  谁在写西加加  阅读(132)  评论(0编辑  收藏  举报