leetcode之路005 Longest Palindromic Substring

本文探讨了多种寻找字符串中最长回文子串的方法,包括基本的双指针法、跳过重复字符的优化策略及Manacher算法。通过具体实例说明了每种方法的实现细节与效率对比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


题目大意:给定一个字符串s,找到其最长的回文子串,假设s的长度最长为1000,并且存在一个唯一的最长子串。返回最长的子串。

题目意思挺清楚的,但是需要考虑多种情况,分别有:

1、s为一个字符,此时s即为所求的回文子串。假定此时刻求出的最长子串长度为max。

2、s中含有“ommo”最常长子串,此时设i指向第一个m,需要判定当前字符和下一个字符是否相同,即s[m]和s[m+1],若相同,则判断s[m-1]和s[m+2],直到不等,求出最长子串长度len1。接着判断s[m-1]和s[m+1]是否相同,直到不等,求出最长子串长度len2。

3、若当前字符和下一个字符不相同,则令len1=0,判断s[m-1]和s[m+1]是否相同,直到不等,求出最长子串长度len2。

4、对len1和len2取最大值,如果大于max,则更新max,并将此时得最长子串赋值给结果字符串resu。

需要注意的是:不管当前字符和下一个字符是否相同,都要进行一下操作:判断s[m-1]和s[m+1]是否相同,直到不等,求出最长子串长度len2。

原因是:s为“baaab”,当执行到i=2指向第一个a时,s[m]和s[m+1]都为a,求得len1=2,然而并不是最长的。继续判断s[m-1]和s[m+1]相同,s[m-2]和s[m+2]相同,得len2=5。所以这点需要注意。

下面是提交ac的代码,测试运行时间为44ms:

#include "stdafx.h"
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;

class Solution {
public:
	string longestPalindrome(string s) {
		int i,j;
		string resu="";
		if(s.length()==1)
			return s;
		int max=1,len1=0,len2=0,len=0;
		for(int i=0;i<s.length()-max/2;++i)
		{ 
			len1=0,len2=0;
			if(s[i]==s[i+1])   //若当前和下一个相同
			{
				j=1;
				while((i-j)>=0&&(i+j+1)<s.length()&&s[i-j]==s[i+j+1])
					++j;
				len1=2*j;
			}
			{    //当前的第下j个和第上j个,<span style="font-size:14px;">s[m-1]和s[m+1]...</span>
				j=0;
				while((i-j)>=0&&(i+j)<s.length()&&s[i-j]==s[i+j])
				++j;
				len2=2*j-1;
			}
			if(len1>len2)
				len=len1;
			else
				len=len2; //取最大
			if(len>max)  //当前较大,更新resu
			{
				max=len;
				resu.clear();
			   resu.insert(0,s,i-(len+1)/2+1,len);
			}
		}
		return resu;
	}
};
int _tmain(int argc, _TCHAR* argv[])
{
	string ss="bb";
	Solution so;
	string rr=so.longestPalindrome(ss);
	cout<<rr<<endl;
	return 0;
}


看统计的运行时间,发现很多能在10ms以下解决,上述思路的时间复杂度为O(N^2),i需要循环整个字符串,对每个s[i]需要往两边扫,直到不相同。唯一进行优化了的措施是,i循环次数为s.length()-max/2,减少了一部分。没想到更好的算法,直接看了讨论区的最佳算法。

代码如下:

string longestPalindrome(string s) {
    if (s.empty()) return "";
    if (s.size() == 1) return s;
    int min_start = 0, max_len = 1;
    for (int i = 0; i < s.size();) {
      if (s.size() - i <= max_len / 2) break;
      int j = i, k = i;
      while (k < s.size()-1 && s[k+1] == s[k]) ++k; // Skip duplicate characters.
      i = k+1;
      while (k < s.size()-1 && j > 0 && s[k + 1] == s[j - 1]) { ++k; --j; } // Expand.
      int new_len = k - j + 1;
      if (new_len > max_len) { min_start = j; max_len = new_len; }
    }
    return s.substr(min_start, max_len);
}


此代码有以下的优化:

1、第6行,即将末尾不需要判断的舍去,上述我也用到了。

2、对于重复元素,直接跳过,比较重复元素块两端的扩展元素是否相同。原因是重复元素无论为多少个,都是满足回文要求的,并且跳过直接判断两端开始,得到的一定是最大长度。此时就可以跳到重复元素块最后位置的下一个位置进行判断了。

例:abcdeeeeeedcmn,当执行到i=4时,一直跳到比较s[i-1]和s[i+6],s[i-2]和s[i+7]等,得到此时得长度max,此时i=5-9都不用计算了,直接跳到i=10。回文子串中,这种情况是常出现的,用自己的方法,有重复元素时,需要执行两个while循环,耗时较多。因此此优化可以大大减小运行时间。

3、因为有判断重复元素,因此不必将回文子串分为奇数和偶数进行处理了,处理更方便。

此外,还有一种O(n)的算法,Manacher算法,参见下面的链接,解释得比较详细

[译+改]最长回文子串(Longest Palindromic Substring) Part II

2016.09.02更新 JAVA代码  

manacher算法,一直拖着没看,今天花时间理解了一番,提交代码如下:

public class Solution {
    public String longestPalindrome(String s) {
		int len = s.length();
		char[] str = new char[2 * len + 1];
		for (int i = 0; i < len; i++) {
			str[2 * i] = '#';
			str[2 * i + 1] = s.charAt(i);
		}
		str[2 * len] = '#'; //添加额外字符奇数偶数统一处理
		int[] parr = new int[str.length];
		int pr = -1;
		int index = -1;
		int max = Integer.MIN_VALUE;
		int maxIndex = -1;
		for (int i = 0; i < str.length; i++) {
			parr[i] = pr > i ? Math.min(parr[2 * index - i], pr - i) : 1; //四种情况 两种直接得到值
			while (i + parr[i] < str.length && i - parr[i] > -1) {  //两种需进行while循环处理
				if (str[i + parr[i]] == str[i - parr[i]]) {     //直接得到值得不进入while
					parr[i]++;
				} else {
					break;
				}
			}
			if (i + parr[i] > pr) {
				pr = i + parr[i];  //更新能到达的最大位置和下标
				index = i;
			}
			if (parr[i] > max) {  //更新最大时的长度和下标
				max = parr[i];
				maxIndex = i;
			}
		}
		char[] res = new char[parr[maxIndex] - 1]; //最大长度时的下标进行子串恢复
		int start = maxIndex - parr[maxIndex] + 1;
		for (int i = 0; i < res.length; i++) {
			res[i] = str[start + 2 * i + 1];
		}
		return String.valueOf(res);
	}

}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值