HJ85 最长回文子串 题解

by 限时烟花

1. 抽丝剥茧

这是一道非常基础的问题,也是每一个学习编程的人都回避不了的问题。

对于第一次接触这个问题的学习者,可以对问题作如下理解:

  1. 回文:即是要求子串本身是对称的;
  2. 最长:即是要求在所有子串中找到符合要求的。

2. 化繁为简

根据上一部分的分析,我们可以很容易的想到暴力的解法:在每一个位置检查所有的合法的长度的子串是否是对称的。

3. 码上行动

while True:
	try:
    	s = input()  # 输入
    	len_s = len(s)
    	result = 0  # result用于记录目前最长的子串长度,初始化为0
    	for n in range(len):  # 从第一个字符的位置开始检查
  			max_len = result + 1  # max_len代表当前检查字符串的长度
       		while n + max_len <= len:  # 代表搜索完以第n+1个字符串开头的所有字符串
          		if s[n:n + max_len] == s[n:n + max_len][::-1]:  # 如果满足是回文字符串
              		result = max_len  # 则此时最大的字符串长度变为max_len
            	max_len += 1  # 每次增加字符串的长度1
    	if result != 0:
       		print(result)
	except:
		break

alt

4. 心有成算

时间复杂度:由于嵌套了双层的循环,而在判断回文时需要进行逐位的判断,共计max_len次,故时间复杂度为O(n3)O(n^3)

空间复杂度:由于使用了n长的临时变量来存储输入,故空间复杂度为O(n)O(n)

5. 另辟蹊径

首先需要明确的是,如果一个字符串是回文字符串,并且长度大于2,那么将其首尾各去掉一个字母后,剩余的部分仍然是回文字符串。

那么根据这样的思路,则使用动态规划的思想我们可以解决这个问题。

使用P(i,j)P(i,j)表示字符串的第ii到第jj个字母组成的字符串是否为回文字符串,那么根据上述的性质,我们容易写出以下的状态转移方程:

P(i,j)=P(i+1,j1) & (Si==Sj)P(i,j) = P(i+1,j-1)\ \&\ (S_i == S_j)

以上是普通情况下的状态转移方程,对于动态规划问题我们还需要考虑边界条件。 在本道题目中,边界条件是当字符串长度为1和2时。 长度为1的字符串显然是回文字符串;对于长度为2的字符串,则需要两个字符相同,故有:

{P(i,i)=TrueP(i,i+1)=(Si==Si+1)\left\{\begin{matrix} & P(i,i) = True \\ & P(i, i + 1) = (S_i == S_{i+1}) \end{matrix}\right.

则可以如下解法:

while True:
    try:
        s = input()
        n = len(s)
        if n < 2:
            print(n)
        
        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
        print(max_len)
    except:
        break

复杂度分析:

  1. 时间复杂度:中间有两层循环,故时间复杂度为O(n2)O(n^2)
  2. 空间复杂度:由于使用n×nn\times n的dp数组,故空间复杂度为O(n2)O(n^2)