Longest Palindromic Substring

【Problem】

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"

【Thinking】

This article is for intermediate readers.

这篇文章是写给中间读者的。

It introduces the following ideas:Palindrome, Dynamic Programming and String Manipulation.

它介绍了下面几个想法:回文(指顺读和倒读都一样的词语),动态规划和字符串处理。

Make sure you understand what a palindrome means。

确定你能够理解回文代表的含义。

A palindrome is a string which reads the same in both directions. For example,”aba” is a palindome,”abc” is not.

回文就是在不同方向能读取到同样的字符串。例如,"aba“是一个回文,"abc"不是。


Approach #1 (Longest Common Substring) [Accepted]

方法#1 (最长公共字串)[可通过]


Common mistake

常见错误


Some people will be tempted to come up with a quick solution, which is unfortunately flawed (however can be corrected easily):

有些人尝试提出快速解法,但是不幸运的是有缺陷(但是可以被很轻松的搞定):


Reverse S and becomeS. Find the longest common substring between S andS', which must also be the longest palindromic substring.

反转S并且成为S'. 找到在S和S'中最长的公共字串,就一定是最长的回文字串。


This seemed to work, let’s see some examples below.

这个看上去是有用的,让我们来看下下面的一些例子。


For example, S=”caba",S′=”abac”

例如 s="caba" , s'="abac"


The longest common substring between S and  S is  ”aba”, which is the answer.

在s和s'的最长公共字串是"aba", "aba"就是答案。


Let’s try another example:S=”abacdfgdcaba”  ,   S′=”abacdgfdcaba”

让我们尝试一下其他的例子:S=”abacdfgdcaba”  ,   S′=”abacdgfdcaba”


The longest common substring between S  andS′  is  ''abacd'' Clearly, this is not a valid palindrome.

最长公共字串在s和s'中则为"abacd". 很清楚,这不是一个有效的回文串。


Algorithm

算法


We could see that the longest common substring method fails

我们可以看到最长公共字串的方法失败了


when there exists a reversed copy of a non-palindromic substring in some other part ofSS.

当s的其他部分存在一个非回文字串的反转字串。


To rectify this, each time we find a longest common substring candidate,

为了解决这个,每一次我们都去找最长公共字串的候选串。


we check if the substring’s indices are the same as the reversed substring’s original indices.

我们检查子字符串的下标是否和反转字符串原始下标一致。


If it is, then we attempt to update the longest palindrome found so far; if not, we skip this and find the next candidate.

如果一致,然后我们尝试更新至今找到的最长回文字符串;如果不是,我们跳过这个然后找寻下一个候选串。


This gives us an O(n^2)O(n2) Dynamic Programming solution which uses O(n^2)O(n2) space (could be improved to use O(n)O(n) space). 

这个给我们提供了O(n^2) 动态规划解法 需要占用 O(n^2)的空间(可以提高到使用O(n)的空间)。

Please read more about Longest Common Substring here.


请点击这里阅读更多关于最长公共子串的内容。点击打开链接

(https://en.wikipedia.org/wiki/Longest_common_substring_problem)


动态规划法可以简单参照下一篇博文

Approach #3 (Dynamic Programming) [Accepted]

To improve over the brute force solution, we first observe how we can avoid unnecessary re-computation while validating palindromes. Consider the case \textrm{''ababa''}”ababa”. If we already knew that \textrm{''bab''}”bab” is a palindrome, it is obvious that \textrm{''ababa''}”ababa” must be a palindrome since the two left and right end letters are the same.

We define P(i,j)P(i,j) as following:

P(i,j)={true,false,if the substring SiSj is a palindromeotherwise.  P(i,j)={true,if the substring Si…Sj is a palindromefalse,otherwise. 

Therefore,

P(i, j) = ( P(i+1, j-1) \text{ and } S_i == S_j )P(i,j)=(P(i+1,j1) and Si==Sj)

The base cases are:

P(i, i) = trueP(i,i)=true

P(i, i+1) = ( S_i == S_{i+1} )P(i,i+1)=(Si==Si+1)

This yields a straight forward DP solution, which we first initialize the one and two letters palindromes, and work our way up finding all three letters palindromes, and so on...

Complexity Analysis

  • Time complexity : O(n^2)O(n2). This gives us a runtime complexity of O(n^2)O(n2).

  • Space complexity : O(n^2)O(n2). It uses O(n^2)O(n2) space to store the table.

Additional Exercise

Could you improve the above space complexity further and how?



其实搞懂了动态规划法,这道题就可以开始写了。

因为暴力破解法是超出了时间限制,所以这里就不详细探讨截取每一个字串进行比较的方法。文末是暴力破解法和动态规划的c++代码。

这道题一共有94个测试用例。

比较典型的有:

abcda
babad
abb
ccd
abcdasdfghjkldcba

现在当我程序出bug时,我就会自己用测试用例推导一遍程序,看看哪里有逻辑错误。

碰到bug,不要急,先把思路理清,实现按着思路来,哪里有问题改哪里,多改改就好了。


一直卡在如何辨别查找的最长公共字串是否是回文字串上?

一开始我认为找到最长公共字串的下标之间的规律即可,随便举了几个例子发现原字串最长的下标+反转子串最长的下标+1=子串的长度,但是测试的时候发现不仅有这个条件,还有反转子串最长子串的第一个字母等于原子串最长子串最后一个字母也成立。

下面是五个典型测试用例,用动态规划的思想,如果两个字母相等,则在左上角的基础上加1.



abcda  
a10001  
d00010  
c00100  
b01000  
a10001  
        
        
        
        
 size-i-1=j      
 babad  
d00001  
a01010  
b10200  
a02030  
b10300  
        
 abb    
b011    
b012    
a100    
        
        
        
     i=1 j=2  
     j+1-longest=i  
        
 ccd    
d001    
c110    
c020    
        
 abcdasdfghjkldcba
a1                
b 2               
c  3              
d   4             
l                 
k                 
j                 
h                 
g                 
f                 
d                 
s                 
a                 
s                 
c                 
b                 
a          


暴力破解法(brute force)[Time Limited  Exceeded]

#include<cstring>
#include <algorithm>
class Solution {
public:
    string longestPalindrome(string s) {
        string rs;
        string sub;
        int position=0;
        int maxlen=0;
        rs=s;
        reverse(s.begin(),s.end());
        for(int i=0;i<rs.length();i++)
        {
            for(int j=rs.length()-i;j>0;j--)
            {
                sub=rs.substr(i,j);
                if(s.find(sub)!=string::npos && maxlen<sub.length() && (s.find(sub)+i+j)==rs.length())
                {
                    maxlen=sub.length();
                    position=i;
                }
            }
        }
        sub=rs.substr(position,maxlen);
        return sub;
    }
};



动态规划(Dynamic Programming)  [235ms]

#include<string>
class Solution {
public:
    string longestPalindrome(string s) {
        string rs=s;
        reverse(s.begin(),s.end());
        int size=s.length();
        if(size==1)
            return rs;
        if(size==0)
            return "";
        int dynamictable[size][size];
        int longest=0;
        int start=-1;
        for(int i=0;i<size;i++)
        {
            dynamictable[0][i]=(s[0]==rs[i])?1:0;
        }
         for(int i=1;i<size;i++)
        {
            dynamictable[i][0]=(rs[0]==s[i])?1:0;
            for(int j=1;j<size;j++)
            {
                    dynamictable[i][j]=(s[i]==rs[j])?dynamictable[i-1][j-1]+1:0;
            }
        }
        for(int i=0;i<size;i++)
        {
            for(int j=0;j<size;j++)
            {
                if((i+j+1==size || rs[j+1-dynamictable[i][j]]==s[i])&& dynamictable[i][j]>longest)
                {
                       longest=dynamictable[i][j];
                        start=j-longest+1;
                }
            }
              
            
        }
     return   rs.substr(start,longest);
    }
};


想对动态规划方法进行优化的同学,可以查看ider的博客 从优化到再优化,最长公共子串

http://blog.iderzheng.com/longest-common-substring-problem-optimization/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

佳悦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值