您的位置:首页 > 其它

LeetCode 5_Longest Palindromic Substring

2015-08-30 11:30 274 查看
很久不写博客了,这段时间有事儿耽误了,还好自己还是是年轻,迟早会写完的。。。

这是leetcode的第五题,难度是中等(Medium),题目本身好像是个经典的问题,不过本人也没看到过,不多说了,上题

Given a string S,
find the longest palindromic substring in S.
You may assume that the maximum length of S is
1000, and there exists one unique longest palindromic substring.

这里有一个专业的词汇 longest
palindromic substring,英文稍微差一点儿的同学可能会有点儿理解上的问题,当然我的英文是“没问题的“,所以我就百度了一下,通过简单的搜索可知它的中文名字叫做“最大回文子串”。这样一来可能会有部分人后悔当初没好好学中文了,先不要着急,我已经为大家百度出了答案。定义我就不说了,简单来说就是一个字符串中最长的对称子串,对称大家都理解吧,比如说abccba,或者eyezeye这种东西。题目剩下的东西就没什么好说的了。

题目大家都理解了,那就开始构思吧:最简单的思路就是先用一个指针历遍整个数组用来作为中间那个位置,然后用两个指针从第一个指针开始往两边历遍,历遍继续的条件是两个对称位置的元素相同(对称嘛)并且不能超出字符串的边界。当历遍停止时两个指针的距离就是以当前中间位置为对称轴时回文子串的长度。

这里有两个关键问题:

(1)对称的情况有两种。中间有字母或者中间没字母,就像上面举得例子一样abccba是中间没字母的(暂且这么叫吧,只有大家理解就好),对称轴是一个空位置(在两个c之间),还有一种是eyezeye这种中间有字母作为对称轴的(z是对称轴)。这个问题不同的人有不同的解决问题,从而会形成很多不同的程序,但算法的大概思路是不会变的,由于以前做过一个类似的问题(其实就是leetcode第四题),所以本人采用了分奇偶的思路(事实证明不算高明)。

(2)边界的问题。这其实是一个,怎么说呢,显而易见的问题。不过在实际处理过程中还是有一些细节需要处理的,这种地方往往考察的是一个程序员的思维严谨性,很不幸本人在这个地方有点儿误入歧途,废了一番功夫。

好了,具体的编码过程就不多说了,大家都经历过,是希望与失望的血泪史,最后本人的代码如下:

{
public:
string longestPalindrome(string s) {
if(s.size()<=1)return s;
string::size_type N=2*s.size();
int num=0;
string::iterator pre,bac;
string::iterator bestPre=s.begin(),bestBac=s.end();
for(string::size_type i=1;i<N;++i)//
{
pre=s.begin()+(i-1)/2;
bac=s.begin()+i/2;
bool flag=false;
while(*pre==*bac)
{
if(pre==s.begin()||bac==s.end()-1)break;//注意点1
pre--;bac++;
}
if(*pre!=*bac)
{
pre++;
bac--;//返回刚才的位置
}

if(bac-pre+1>num)
{
num=bac-pre+1;
bestPre=pre;
bestBac=bac;
}

}
string subString(bestPre,bestBac+1);
return subString;
}
};


说实在的代码质量一般,不过这个过程中还是有几点需要说明的:

(1)极端输入的处理。这是一个程序员必须考虑的一个问题,直接关系到程序的正确与否,因为编程的构思大都是在”正常的“输入情况下做出的。但对于一些“不正常但合法”的输入会有问题。想本题这样涉及字符串的,不用说,空串啊,一个字母啊,甚至两个字母啊这种肯定是要重新考虑的。一些数字的可能要考虑0啊,1啊,负数啊这些情况,这里就不多说了。总之if(s.size()<=1)return
s;是用来处理极端输入的。

(2)string类型的begin()前面一个位置是地雷。这是C++语言的一个特性,大家都知道,string类型对象有一个begin()操作,返回的是对象的首位地址,类型的end()返回的是最后一个位置的下一个地址,但end()并不指向数据专门用来作为“哨兵”,这个一般人也都明白。但在程序中遇到需要begin()前面的“哨兵”时会遇到意想不到的错误:像

iter != s.begin()-1这种操作是不允许的!实际上,(这里是重点,要重点理解一下)只要有任何一个指针指向了begin()前面一个位置,那程序马上就会报错。本人在这个地方纠缠了很久,最后用了上面的方法解决(算是解决吧)。

其他部分还算好理解吧,就不多说了,下面直接看一下大神的代码吧:(在这里感谢 prime_tang的分享)

class Solution {
public:
std::string longestPalindrome(std::string s) {
if (s.size() < 2)
return s;//特殊情况特殊处理
int len = s.size(), max_left = 0, max_len = 1, left, right;
for (int start = 0; start < len && len - start > max_len / 2;)
{
left = right = start;
while (right < len - 1 && s[right + 1] == s[right])
++right;//当中间有重复时跳过

start = right + 1;

while (right < len - 1 && left > 0 && s[right + 1] == s[left - 1])
{
++right;
--left;
}

if (max_len < right - left + 1) {
max_left = left;
max_len = right - left + 1;
}
}
return s.substr(max_left, max_len);
}
};
其实大神的算法思路和我是一样的,高明的地方有这么几点吧:

(1)用不同的变量标识下标。这一点是显而易见的,直接用start、left这种符号使代码的可读性有了很大的提高,而且思路很清晰。相比之下,我的i啊j的就不那么高明了,这除了纯粹的符号问题外还有一个对前面提到的那个对称分两种情况处理方式不同的问题。我们下面再细说。

(2)充分分析理解了最大回文子串的特性。最外面那个for循环中的len - start > max_len / 2条件的意思是当剩余的字符串长度小于当前获得的最大回文子串长度的一半时就不用继续搜索了。这个由回文子串的性质很容易理解,但人家能想到并利用确实值得我这种“野蛮人”学习。

(3)跳过连续重复的字母。这是一个最值得学习的地方,是对最大回文子串的一个特性的深度挖掘。其特点是最大回文子串的对称轴不可能是连续字母不对称的位置,而连续字母都可以看做两个字母(中间省略)。这么说大家肯定不能理解,让我们举个例子,比如dabbbbb...bbbbbac。首先第一个,所有b中只有中间那个两个b空位之间可能是最大回文子串的中间轴,而其他的b都不可能,这个跟最大回文子串中的“最大”有关,不需要过多解释。二是中间有多少b没有关系(无论奇数偶数),因为有多少都是对称的。所以要查找就直接从第一个b前面和最后一个b后面开始就可以了。这就是第一个while做的事情,这样一来还巧妙的避免了对称轴分类问题,确实很高明。

不多说了,最后总结一下:这次设计的思路是确定的,当然也许有更好的思路,不过从leetcode统计的算法时间来看目前还没有跨越性的好算法出现。大神的算法时间为4ms,可以说是C++中最好的了,而我的是200多ms,这一点我有点儿不明白,算法没有实质差别,时间怎么会差这么多呢?估计最大的差距还是分奇偶的地方。怎么说呢,差距不小,我们还需要继续努力啊。

最后分享一句名言,与大家共勉:勤能补拙是良训,一分辛苦一分才!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: