[ LeetCode ] 5. Longest Palindromic Substring (C++ & Python)

本文详细探讨了LeetCode上的5. Longest Palindromic Substring问题,给出了C++和Python的多种解法,包括暴力解法、从中心展开的优化解法、动态规划等,并分析了不同解法的时间复杂度和内存使用情况。

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

题目:

5. Longest Palindromic Substring

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

Input: "cbbd"
Output: "bb"

题意:

给一个字符串s,找出s中所包含的最长回文字符串(回文字符串:即关于某一个字符或某个位置呈对称,比如aba、abba、adsda就是,abc、abdc就不是),s的长度最长为1000个字符。

分析:

题意很简单,给出以下几种解法

  1. 第一种:简单,暴力,遍历从每一个字符开始,每一个长度,判断是否回文,最后输出长度最大的回文字符串。实现见Code1.
  2. 第二种:由于回文字符串关于某一字符或某一位置成镜像,因此可从给定字符向两边展开,查找以某一字符为中心的最长回文字符串。实现见Code2.
  3. 第三种(C++最优解):基于第二种方法可以得知Method 2并不是最优解,方法三主要是针对方法二进行改进完善。见Code3.
  4. 第四种:使用DP解决此问题。见Code4.
  5. 使用Python复现C++最优解。见Code5.

Code1

class Solution {
public:
    string longestPalindrome(string s) {
			int max=1;
			string ans=s.substr(0,1);
			for(int i=0; i<s.size(); ++i){
				for(int j=s.size()-i; j>1; --j){
					int imin=i, imax= i+j-1;
					while(imin<=imax && s[imin]==s[imax]){
						++imin;--imax;
					}
					if(imin>imax){
						if(j>max){
							max=j;
							ans = s.substr(i,j);
						}
					}
				}
			}
			return ans;
    }
};

做这道题想到的第一种思路,一开始本地实现之后,上传之后直接爆掉了,后加修正完善之后,勉强通过,但依旧没有效率可言。结果如下:

Runtime: 2744 ms, faster than 2.99% of C++ online submissions for Longest Palindromic Substring.

Memory Usage: 9.4 MB, less than 100.00% of C++ online submissions for Longest Palindromic Substring.

没错,这是C++实现的结果,效率恐怕相比python都要慢一个量级。分析原因可以发现,每次循环判断都是从头开始,因此包含了大量的无用计算,比如已经判断出ada为回文,那么这时直接判断ada两边的字符即可判断出当前位置是否存在以字母d为中心长度更长的回文字符串,比如xadax。所以如果使用展开的方式替换上面暴力循环的方式 能够节省很多不必要的计算。实现如下:

Code2:

class Solution {
public:
    // 求从 left 和 right开始展开 符合回文的最长字符串长度
    int expandCenter(string &s, int left, int right){
			int l=left, r=right;
			while(l>=0 && r<s.size() && s[l]==s[r]){
				--l;
				++r;
			}
			return r-l-1;
		}
    string longestPalindrome(string s) {
			if(s.size()<=0) return "";
			if(s.size()==1) return s;
			int st=0,e=0;
			for(int i=0;i<s.size();++i){
                                // 从某一字符展开,应对absfsba这种情况
				int len1 = expandCenter(s,i,i);
                                // 从两个相同的字母之间开始展开,应对 asddsa这种情况
				int len2 = expandCenter(s,i,i+1);
                                // len 表示 从当前位置展开得到的最长回文串长度
				int len = max(len1, len2);
				if(len>(e-st+1)){
                                        // (len-1)/2 表示取得的回文串在展开中心最前面的字符
					st = i - (len-1)/2;
					e = i + len/2;
				}
			}
			return s.substr(st,e-st+1);
    }
};

平均时间复杂度为O(n2),结果如下:

Runtime: 24 ms, faster than 84.23% of C++ online submissions for Longest Palindromic Substring.

Memory Usage: 9.4 MB, less than 100.00% of C++ online submissions for Longest Palindromic Substring.

可以看出,使用展开的方式 虽然还不是最优解,但相比之前暴力解法 效率也有了极大的提升。

对比方法2和方法1,效率提升的原因时因为我们去除了大量不必要的循环冗余,那么遵循同样的思路 可不可以用来再次改善方法2呢?

观察方法2可以得知,是通过在每一个字符串开始呈镜像搜索匹配,那么这里是否有必要呢? 比如字符串 "baaaaaaaaaaaaab" 在除最中间的a以外的展开都是徒劳无用的,因此 方法3主要针对这种情况进行完善,方法也很简单 遇到相邻且相同的字符,可以直接以这些相同的字符构成的字符串为中心进行展开搜索,实现如下:

Code3

class Solution {
public:
    string longestPalindrome(string s) {	
			if(s.size()<=0) return "";
			if(s.size()==1) return s;
			int imin=0, imax=0, maxlen=0;
			int i=0, len=s.size();
			while(i<len){
				int sl=i, sr=i;
				while(sr<len-1 && s[sr+1]==s[sr]) sr++;
				i=sr+1;
				while(sr<len-1 && sl>0 && s[sr+1]==s[sl-1]){
					++sr;
					--sl;
				}
				int nowLen = sr-sl+1;
				if(nowLen>maxlen){
					maxlen = nowLen;
					imin = sl;
				}
			}
			return s.substr(imin,maxlen);
    }
};

结果:

Runtime: 8 ms, faster than 100.00% of C++ online submissions for Longest Palindromic Substring.

Memory Usage: 9.4 MB, less than 100.00% of C++ online submissions for Longest Palindromic Substring.

对比可以发现,算法执行效率相比前面两种已经有了质的提升。

Code4:

class Solution {
public:
    string longestPalindrome(string s) {
			
			int len = s.size();
			if(len<=0) return "";
			if(len==1) return s;
			int a[len][len];
			for(int i=0;i<len;++i){
				a[i][i]=1;
				for(int j=i+1;j<len;++j){
					a[i][j]=0;
				}
			}
			int maxlen=1, imin=0;
			for(int slen=2; slen<=len; ++slen){
				for(int i=0;i+slen-1<len;++i){
					int j = i+slen-1;
					if((slen==2 || a[i+1][j-1]==1) && s[i]==s[j]){
						a[i][j]=1;
						if(maxlen < slen){
							maxlen = slen;
							imin=i;
						}
					}
				}
			}
			return s.substr(imin,maxlen);
    }
};

本方法使用动态规划解决,通过观察可以发现,最长回文串一定是基于前面回文串向两端扩展得到的。因此,每一个位置判断是否为回文,只需要在前面得到的结果上面加一些条件即可,比如P(i,j) = P(i-1,j+1) and str[i]==str[j] 。

上面解决方法中,我们使用二维数组a表示是否字符串[i,j] 是否为回文串,初始只有单个字符为回文,后面每一步判断均参照前面的结果。

结果:

Runtime: 156 ms, faster than 36.93% of C++ online submissions for Longest Palindromic Substring.

Memory Usage: 13.9 MB, less than 100.00% of C++ online submissions for Longest Palindromic Substring.

可以看出,针对本题使用DP并不是一个很好的解决方案,但在其他场景下DP往往是至关重要的解决方案。

 

Code5

class Solution:
    def longestPalindrome(self, s: 'str') -> 'str':
        slen = len(s)
        if slen<=0: return ""
        if slen==1: return s
        imin, imax=0, 0
        maxlen=1
        for i in range(slen):
            sl, sr = i, i
            while sl<slen-1 and s[sl]==s[sl+1]:
                sl+=1
            i = sl+1
            while sr>0 and sl<slen-1 and s[sr-1]==s[sl+1]:
                sr-=1
                sl+=1
            nowlen = sl-sr+1
            if nowlen>maxlen:
                maxlen = nowlen
                imin = sr
                imax = sl
        return s[imin:imax+1]

结果:

Runtime: 940 ms, faster than 72.81% of Python3 online submissions for Longest Palindromic Substring.

Memory Usage: 12.4 MB, less than 100.00% of Python3 online submissions for Longest Palindromic Substring.

可见,对于python而言 C++的最优解题思路并不一定是最适合python 的方法。具体如何提升python结果的效率,大家可以多做尝试。

 

Status:ACCEPT

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值