647. 回文子串,516.最长回文子序列

发布于:2025-07-31 ⋅ 阅读:(23) ⋅ 点赞:(0)

647. 回文子串

这道题不能以一个一维数组来表示回文字串的数目,如dp[i]表示为 下标i结尾的字符串有 dp[i]个回文串的话很难找到递归关系。因为你不清楚引入下标 str[i+1] 后,当前dp[i+1]与dp[i]的数量关系。要抓住回文字串的特征,回文串的特征在于对称。因此可以通过定义一个二维数组dp[i][j]来定义和判断下标为i 到下标为j的字符串str[i: j+1]是否是回文子串。

思路:

如果 [i], [seq], [j] 中 [seq] 已经被判定是回文子串了,那么如果 [i] == [j],此时就可以判定 ( [i],[seq],[j] )构成的整个字符串就是回文子串

1. dp数组定义

  • dp[i][j]: 表示从 下标i 到 下标j 所表示的字符串 是否是回文字串

2. 递推公式

if str[i] == str[j]:

    if j - i <= 1: ### 长度小于等于2的字符串容易判断出是否是回文子串

        dp[i][j] = True

        result += 1

    else:

        if dp[i+1][j-1] == True:

        dp[i][j] = True

        result += 1

3. dp初始化

  • 初始化为一个全False的二维数组

4. 遍历顺序

  • 根据dp[i][j]的递推公式(if str[i] == str[j] and dp[i+1][j-1] == True then dp[i][j] = True)可知,i得从大到小进行遍历,j得从小到大进行遍历

另外,要注意 j >= i,因此在对 j 进行循环遍历时要注意循环的起始位置。

i == j 的时候,表示当前判断的是同一个位置,即判断的是一个字符。

5. 打印dp数组

  • result 用于统计回文子串的数目
  • dp用于统计回文子串的位置

Code

class Solution(object):
    def countSubstrings(self, s):
        """
        :type s: str
        :rtype: int
        """
        length = len(s)
        dp = [ [False] * len(s) for _ in range(len(s)) ] 

        result = 0

        for i in range(len(s)-1,-1,-1):
            for j in range(i, len(s)):
                if s[i] == s[j]:
                    if j - i <= 1:
                        dp[i][j] = True
                        result += 1
                    else:
                        if dp[i+1][j-1] == True:
                            dp[i][j] = True
                            result += 1
        
        # for i in range(len(s)):
        #     print(dp[i])
        # print(dp)
        return result

516.最长回文子序列 

与上一题相比,区别在于 序列子串序列是只要相对顺序是正确就行,子串一定是要绝对顺序是正确的,如bbbab,最长回文子序列是bbbb,最长回文子串是bbb。

因此,与上一题相比,关键在于进行删除操作。

关键在于dp数组的定义,要明白与647的区别。dp数组定义正确的话,后面递推公式递推出来后就知道如何开展工作了。647如果 dp[i][j] 定义为 [i,j]  之间的最长回文子串数目的话,其实也能做,递推公式如下:

dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]
if str[i] == str[j]:
    dp[i][j] += 1

但这样的话,感觉理解起来不如(dp[i][j]: 表示从 下标i 到 下标j 所表示的字符串 是否是回文字串)简单,后者基于dp数组的定义可以较为清楚地得出递推公式。

思路:

1. dp数组定义.

dp[i][j] 表示以 [i,j] 之间的最长回文子序列长度

2. 递推公式

if s[i] == s[j]: ## 不进行删除操作
    dp[i][j] = dp[i+1][j-1] + 2
else:
    dp[i][j] = max(dp[i][j-1], dp[i+1][j]) ## 进行删除操作,取最大

由于s[i] != s[j]:

因此对于这两个元素,我们可以对其其中一个进行删除操作来得到 [i,j] 内的最大回文子序列长度

dp[i][j] = dp[i][j-1]:表示删除 第 j 个元素,只考虑 [i, j-1] 内的最大回文子序列长度

dp[i][j] = dp[i+1][j]:表示删除 第 i 个元素,只考虑 [i+1, j]内的最大回文子序列长度

3. dp初始化

最小的回文子序列为一个字符的大小。

for i in range(length):
   dp[i][i] = 1            # 最小的回文子序列为一个字符的大小

为什么要初始化 i == j 的情况是因为第二个循环是从i+1开始遍历操作的,在此过程中i 和 j 会不断逼近,但始终构不成 i == j。

4. 遍历顺序

从下到上,从左到右。

为什么与647相比,本题第二个循环从 i+1开始,因为647的第二个循环 i == j 的情况起到了初始化的作用,只不过一并放到第二个循环进行操作而已,如果提前初始化了,则647的第二个循环也是从i+1开始。在这道题中,我们已经初始化了 i==j 的情况,因此第二个循环要从 i + 1 开始。

5. 打印dp数组。

dp [0][-1] 表示整个字符串内的最大序列长度。

Code

class Solution(object):
    def longestPalindromeSubseq(self, s):
        """
        :type s: str
        :rtype: int
        """
        length = len(s)
        dp = [ [0] * length for _ in range(length) ]

        for i in range(length):
            dp[i][i] = 1            # 最小的回文子序列为一个字符的大小
        
        for i in range(length-1,-1,-1):
            for j in range(i+1,length):
                if s[i] == s[j]:        ## 不进行删除操作
                    if j-i == 1:
                        dp[i][j] = 2
                    else:
                        dp[i][j] = dp[i+1][j-1] + 2
                else:
                    dp[i][j] = max(dp[i][j-1], dp[i+1][j])      ## 进行删除操作,后判断取最大
        
        return dp[0][length-1]


网站公告

今日签到

点亮在社区的每一天
去签到