题解 | #最长回文子串#

最长回文子串

http://www.nowcoder.com/practice/12e081cd10ee4794a2bd70c7d68f5507

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)
全部评论
第一个可以从最长的开始判断,依次减少子串的长度,直到出现符合的回文子串,这样就不用记录最长的子串长度,第一个找到的子串一定是最长的
1 回复 分享
发布于 2022-04-22 21:49
仿照up写了个java版 import java.util.*; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); // 注意 hasNext 和 hasNextLine 的区别 while (in.hasNext()) { // 注意 while 处理多个 case String input = in.next(); int N = input.length(); if (N < 2) { System.out.println(N); return; } //最长回文子串长度 int max_len = 1; //dp[i][j]表示子串的左端点是i,右端点是j boolean[][] dp = new boolean[N][N]; for (int i = 0; i < N; i++) { dp[i][i] = true; } //子串长度 for (int L = 2; L <= N; L++) { //i左端点,j右端点 int i = 0; int j = i + L - 1; while (i < N && j < N) { // System.out.printf("i=%d,j=%d\n",i,j); char c1 = input.charAt(i); char c2 = input.charAt(j); if (c1 != c2) { dp[i][j] = false; } else { if (L == 2) { dp[i][j] = true; } else { dp[i][j] = dp[i + 1][j - 1]; } } if (dp[i][j] && L > max_len) max_len = L; i++; j = i + L - 1; } } // ArrayUtil.print2DimensionBooeanArray(dp); System.out.println(max_len); } } }
1 回复 分享
发布于 2023-03-13 21:38 重庆
请问一下,第一种解法的第6行应该是len_s是吗
点赞 回复 分享
发布于 2022-03-22 14:40
嗯,应该是len_s
点赞 回复 分享
发布于 2022-03-22 23:25

相关推荐

头像
11-07 01:12
重庆大学 Java
精致的小松鼠人狠话不多:签哪了哥
点赞 评论 收藏
分享
51 13 评论
分享
牛客网
牛客企业服务