最长子回文串的三种解法

最长回文子串的三种解法

0 问题描述

给定一个字符串,求其中包括的最长回文子串,返回最长回文串

0.1 问题分析

(1)回文字符串:即一个字符串与其逆置后的字符串相同,就称其为回文字符串。

(2)算法思路:找最长回文子串=枚举子串+判断回文+取长度最大

  • part 1: 枚举子串

    使用两层for循环遍历字符串s,如下图所示:

    在这里插入图片描述

    代码:

    # 枚举首元素
    for start in range(len(s)):
        #枚举尾元素
    	for end in range(strat+1:len(s)+1):
            #1.判断 s[start:end] 是否是回文字符串
            #2.若是且该串长度大于max_len,则更新begin 和 max_len
    
  • part 2: 判断回文

    • 方法(1)

      • 思路: 回文字符串倒置前后不变

      例子:

      在这里插入图片描述

      • 代码:
      if s[start:end+1]==s[start:end+1:-1]:
      	return True
      else 
      	return False
      
      • 代价:
      空间上,增加了一个字符串数组的空间 ,O(n)
      
    • 方法(2)

      • 思路:如果一个字符串的第i个字符都与其倒数第i个字符相等,那么他就是回文串

      • 故设立两个指针stat 和 end 起始指向首尾,并同步向中间移动

        在这里插入图片描述

      • 代码:

        while start<end:
        	if s[start]==s[end]:
                start += 1
                end   -= 1
        		continue
        	else:
        		return Flase
        
        return True
        
      • 代价:

        在方法1的基础上节约了空间的使用

  • part 3 取长度最大的字符串

    • 设定begin和max_len两个变量

      begin = 0 #记录最长字符串的首字符下标
      max_len = 1 #记录最长字符串的长度
      

(3)算法优化:(2)中的三部分是解决问题的三个关键点。在具体的实现上,各个部分的实现方式都会影响最后程序的时间和空间复杂度,即算法的性能。所以如果要优化算法可以从这三部分的实现方式上下手。

1.暴力枚举法

1.1 思路:

找出该字符串中的所有子串,然后依次判断该子串是否为回文

在这里插入图片描述

1.2 代码:

#判断是否回文串
def huiwen(s:str)->int:
    if len(s) == 1:
        return 1

    if len(s)%2==0:
        i = 0
        j = -1
        while(i+1-j <= len(s)):
            if(s[i]!=s[j]):
                return 0
            i+=1
            j-=1
        return 1
    
    else:
        i = 0
        j = -1
        while(i+1-j<=len(s)-1):
            if(s[i]!=s[j]):
                return 0
            i+=1
            j-=1
        return 1


class Solution:
    def longestPalindrome(self, s: str) -> str:
        # 1. 暴力法
        # 1.1 找出所有字串并存入A1中
        A1 = []
        for i in range(len(s)):
            t = s[i]
            A1.append(t)
            for j in range(i+1,len(s)):
                t += s[j]
                A1.append(t)

        # 1.2 从所有子串中找出回文串
        max_len = 0
        max_s = "" 
        for a in A1:
            if huiwen(a)==1:
                if len(a)>max_len:
                    max_len = len(a)
                    max_s = a

        return max_s

        

                


1.3 代价:

时间复杂度空间复杂度
O(n^3)O(1)

2.中心扩展法

2.1 算法思路

遍历整个字符串,把每个字符当做回文的中心。即在渐进子串的同时实现了回文串的判断。

在这里插入图片描述

流程:

  • 1.首先判断字符串的长度n是否小于2,如果是,则直接返回该字符串,因为长度为1的字符串本身就是回文串,长度为0的字符串也可以看作是回文串。

  • 2.初始化一个空字符串ans,用来存储最长回文子串。

  • 3.使用for循环枚举字符串中的每个字符i,将其作为回文串的中心位置。

  • 4.分别考虑回文子串长度为奇数和偶数的情况,对于每种情况,向两端扩展,直到不能扩展为止,然后比较得到最长回文串。

  • 5.如果找到了更长的回文子串,则更新ans。

  • 6.遍历完所有中心位置后,返回ans。

在具体实现时字符串的长度的奇偶会导致回文中心不同,两种情如下所示:

(1)奇数时

在这里插入图片描述

(2)偶数时

在这里插入图片描述

2.2 代码

class Solution:
    def expandAroundCenter(self, s, left, right):
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        return left + 1, right - 1

    def longestPalindrome(self, s: str) -> str:
        start, end = 0, 0
        for i in range(len(s)):
            left1, right1 = self.expandAroundCenter(s, i, i)#检测奇数回文串
            left2, right2 = self.expandAroundCenter(s, i, i + 1)#检测偶数回文串
            
            if right1 - left1 > end - start:
                start, end = left1, right1
            if right2 - left2 > end - start:
                start, end = left2, right2
        return s[start: end + 1]

			
					
			

2.3 代价:

时间复杂度空间复杂度
O(n^2)O(1)

3.动态规划法

3.1问题分析

	对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。例如对于字符串 “ababa”,如果我们已经知道 “bab” 是回文串,那么“ababa” 一定是回文串,这是因为它的首尾两个字母都是 “a”。

	动态规划有**三要素**:
  • (1)状态: dp [i] [j] 表示字符串 s[i…j] 是否是回文的。
  • (2)状态转移方程: dp [i] [j] = dp [i+1] [j-1] and s [i]==s[j]
  • (3)边界条件:j - i < 3 , 即子串的长度为 1 或 2

3.2 算法思路

(1)初始化二维数组dp

  • 数组dp: 创建一个二维数组dp。dp [i] [j] 表示字符串 s[i…j] 是否是回文的。
  • 初始化 dp[i] [i] = TRUE,其余为flase
babab
01234
b0T
a1T
b2T
a3T
b4T
  • (2) 根据状态转移方程: dp [i] [j] = (dp [i+1] [j-1] or (j - i < 3 ) ) and (s[i]==s[j] ) 填数组dp:
    • 由于状态转移取决于其左下角元素,

    • 故先升序填列,再升序填行

    • 每填一个true,判断 j-i+1是否大于max_len:
      若大于则更新begin = i,max_len = j-i+1

过程:

“b a”: dp [0] [1] = (dp [1] [0] | (1-0<3)) & (s[0]==s[1])
= (0|1)&(0) = 1&0 = 0
dp [1] [0] = F

beginmax_lenbabab
0101234
b0TF
a1T
b2T
a3T
b4T

在这里插入图片描述

  • 结果:
beginmax_lenbabab
0501234
b0TFTFT
a1TFTF
b2TFT
a3TF
b4T
  • 返回 s[begin:begin+max_len] = “b a b a b”

3.3 代码实现

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)# 字符串长度

        if n < 2:
            return s
        
        max_len = 1
        begin = 0 #左指针
        # dp[i][j] 表示 s[i..j] 是否是回文串
        dp = [[False] * n for _ in range(n)]
        for i in range(n):
            dp[i][i] = True
        
        # 递推开始
        # 先枚举子串长度
        for L in range(2, n + 1):
            # 枚举左边界,左边界的上限设置可以宽松一些
            for i in range(n):
                # 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                j = L + i - 1
                # 如果右边界越界,就可以退出当前循环
                if j >= n:
                    break
                    
                if s[i] != s[j]:
                    dp[i][j] = False 
                else:
                    if j - i < 3:
                        dp[i][j] = True
                    else:
                        dp[i][j] = dp[i + 1][j - 1]
                
                # 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    begin = i
        return s[begin:begin + max_len]
           dp[i][j] = True
                    else:
                        dp[i][j] = dp[i + 1][j - 1]
                
                # 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    begin = i
        return s[begin:begin + max_len]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值