代码随想录27期|Python|Day52|​动态规划|​647. 回文子串|516. 最长回文子序列

 本文是动态规划的回文字符串部分。

647. 回文子串

本题需要搞清楚dp的定义、遍历顺序和递推公式。

1、dp数组的定义

由图片可知,不同于之前的dp数组直接定义为当前遍历到的位置处题目所要求得值,而是应该定义为i为开始,j为结束的子串是否是回文串

这是因为之前的遍历过程一般都是单向的,而回文串需要两遍同时进行扩展判断,所以不能够只保留遍历到“某一处”位置的回文串个数。

所以,dp的定义发生了改变,定义一个子序列是否是回文串。

2、递推公式

随着dp定义发生的变化,递推公式也需要进行修改。

具体来讲包括:结果个数的更新、向两侧扩展子串是否是回文串的判断。

2.1当前i和j所指向的字符相等

(1)如果只有一个元素,也就是j-i=1,那么肯定这个单一的字符是回文串;此时res += 1,同时需要把此处的dp[i][j] = True标记为回文串。

后来发现,其实此处的单个字符就是最小的回文串单元,它们构成了其余回文串判断的基础

(2)如果有2个以上的元素,此时就需要进行判断了。

判断的依据是依靠上一个状态,也就是dp[i+1][j-1],也就是在[i, j]这个区间内分别往内收缩一个索引的区间。

如果这个收缩的区间是True(回文串),那么[i, j]区间肯定也是回文串。此时res += 1,并把dp[i][j] = True。

2.2当前i和j所指向的字符不相等

如果不相等的话这个区间就会被设置为False。实际上在初始化的时候已经全部置为Fasle了,所以不需要再进行操作。

3、初始化

初始化其实是根据定义来的,定义为判断i,j是回文子串后设置为True,那么初始化全部置为False即可,表示全部都不是回文串。

4、遍历顺序

遍历顺序由递推公式确定。

在递推公式中,除了直接对s中字符的比较之外,还需要依靠dp[i+1][j-1]来进行更新,可以看到这个dp的位置在当前位置的左下方

那么,遍历的顺序就相应地i从大到小(从下往上)、j从小到大(从左往右)

class Solution(object):
    def countSubstrings(self, s):
        """
        :type s: str
        :rtype: int
        """
        dp = [[False] * len(s) for _ in range(len(s))]
        res = 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
                        res += 1
                    elif dp[i+1][j-1] == True:
                        dp[i][j] = True
                        res += 1
        return res

本题和之前题目的根本区别在于dp数组的定义需要根据回文串判断的依据,同时向两边扩展,这决定了dp没法直接设置为题目所要求的个数,而是表征区间是否是回文串。

双指针解法

双指针解法主要是在遍历的是否分别以当前单个字符和两个字符为中心向左右延伸,统计满足条件的情况。

class Solution(object):
    def countSubstrings(self, s):
        """
        :type s: str
        :rtype: int
        """
        # 双指针
        res = 0
        for i in range(len(s)):
            res += self.extend(s, i, i, len(s))  # 单个字符作为中心向两边延伸
            res += self.extend(s, i, i+1, len(s))  # 两个字符作为中心向两边延伸
        return res

    def extend(self, s, i, j ,length):
        res = 0
        # 如果当前字符串满足回文串的要求,且区间首位在整个字符串内
        while i >= 0 and j < length and s[i] == s[j]:
            i -= 1  # 向左扩张
            j += 1  # 向右扩张
            res += 1  # 结果增加1
        return res

516. 最长回文子序列

本题在原来的基础上加上了自由删减的能力,所以在进行更新的时候会有所区别,和之前做的编辑距离类的更新方式类似。

1、dp定义

dp[i][j]定义为在i,j区间内的最长回文子串的长度,注意,这里是“包含”回文子串,和上一题的要求“必须以i和j为边界”不同

2、dp更新

对于dp的更新分为两个方面:

(1)s[i] == s[j],此时相当于当前满足回文子串的要求,由于可以删除不要的元素,所以只要两个字符相等,就可以在原来dp数组的基础上+2

(2)字符不相等时,分为两种情况:

<1>i索引向内收缩,变为i+1,j索引hold住不变(编辑距离的处理方式),此时dp[i][j] = dp[i+1][j];

<2>j索引向内收缩,变为j-1,i索引hold住不变(也是编辑距离的方式),此时dp[i][j] = dp[i][j-1]

综上,当字符不相等的时候,dp[i][j] = max(dp[i+1][j], dp[i][j-1]),相当于保持两个边界分别向内收缩后的最大值。

3、初始化

由于单个字符自身就算是最小的字符串单位(上一题的结论),所以初始化就是对角线元素全部设置为1(i=j的时候)。

4、遍历顺序

从图上和2、中的更新所用到的索引位置来看,dp更新需要左边、下边、左下方向的dp数组来更新,所以依然是外层i从大到小,内层j从小到大

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

        for i in range(len(s)-1, -1, -1):
            for j in range(i + 1, len(s)):
                if s[i] == s[j]:
                    dp[i][j] = dp[i+1][j-1] + 2
                else:
                    dp[i][j] = max(dp[i+1][j], dp[i][j-1])
        return dp[0][-1]

Day52完结!!!

最长回文子串是指在一个字符串中最长回文子序列回文是指正着读和倒着读都一样的字符串。动态规划是解决最长回文子串问题的一种常用方法。动态规划的思想是将问题分解成子问题,通过求解子问题的最优解来得到原问题的最优解。在最长回文子串问题中,我们可以使用一个二维数组dp[i][j]来表示从i到j的子串是否为回文子串。如果dp[i][j]为true,则表示从i到j的子串回文子串,否则不是。我们可以通过以下步骤来求解最长回文子串: 1. 初始化dp数组,将所有dp[i][i]都设置为true,表示单个字符是回文子串。 2. 遍历字符串s,从长度为2的子串开始,依次判断每个子串是否为回文子串。如果是,则将dp[i][j]设置为true。 3. 在遍历的过程中,记录最长回文子串的长度和起始位置。 4. 最后,通过起始位置和长度来截取最长回文子串。 下面是一个示例代码,可以帮助你更好地理解动态规划求解最长回文子串的过程: class Solution { public: string longestPalindrome(string s) { int len=s.size(); if(len<2) return s; bool dp[len][len];//布尔型,dp[i][j]表示从i到j是否构成回文 int max_count=1;//最大字串的长度 int start=0;//最长字串的起始位置 for(int j=0;j<len;j++) { for(int i=0;i<j;i++) { if(s[i]!=s[j]) dp[i][j]=false; else if((j-i)<3)//(j-1)-(i+1)+1<2表示dp[i][j]的最大字串长度为1 dp[i][j]=true; else { dp[i][j]=dp[i+1][j-1]; } if((j-i+1)>max_count&&dp[i][j]) { max_count=j-i+1; start=i; } } } return s.substr(start,max_count);//截取字符串 } };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值