【2021/5/24】回文子串与中心扩展法、动态规划法



回文子串

【题目】

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

  • 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

  • 输入:“abc”
  • 输出:3
  • 解释:三个回文子串: “a”, “b”, “c”

示例 2:

  • 输入:“aaa”
  • 输出:6
  • 解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”

提示:

  • 输入的字符串长度不会超过 1000 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/palindromic-substrings
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

【中心拓展法】

计算有多少个回文子串的最朴素方法就是枚举出所有的回文子串,而枚举出所有的回文字串又有两种思路,分别是:

  • 枚举出所有的子串,然后再判断这些子串是否是回文。时间复杂度是O(n^3)。
  • 枚举每一个可能的回文中心,然后用两个指针分别向左右两边拓展,当两个指针指向的元素相同的时候就拓展,否则停止拓展。时间复杂度是O(n^2)。
  • 所以选择第二种方法来枚举所有的回文子串。

如何有序地枚举所有可能的回文中心?

需要考虑回文长度是奇数和回文长度是偶数的两种情况:

  • 如果回文长度是奇数,那么回文中心是一个字符。
  • 如果回文长度是偶数,那么中心是两个字符。

借鉴该思想写出的代码:

class Solution:
    def countSubstrings(self, s: str) -> int:
        s=list(s)
        n=len(s)
        i=0
        res=0
        while(i<n):
            j=i
            k=i
            while(k>=0 and j<n):
                if s[k]==s[j]:
                    res+=1
                    k-=1
                    j+=1
                else:
                    break
            j=i+1
            k=i
            while(k>=0 and j<n):
                if s[k]==s[j]:
                    res+=1
                    k-=1
                    j+=1
                else:
                    break
            i+=1
        return res
# 执行用时:100 ms, 在所有 Python3 提交中击败了96.27%的用户
# 内存消耗:15 MB, 在所有 Python3 提交中击败了55.83%的用户

也可以用一个循环搞定。

  • 长度为 n 的字符串会生成 2n-1 组回文中心。
  • 其中左中心为 i / 2,右中心为 i / 2+(i mod 2)。

所以只要从 0 到 2n - 2 遍历 i,就可以得到所有可能的回文中心,这样就把奇数长度和偶数长度两种情况统一起来。

借鉴该思路写出的代码:

class Solution:
    def countSubstrings(self, s: str) -> int:
        s=list(s)
        n=len(s)
        i=0
        res=0
        while(i<2*n-1):
            j=i//2
            k=i//2+i%2
            while(j>=0 and k<n and s[k]==s[j]):
                res+=1
                j-=1
                k+=1
            i+=1
        return res
# 执行用时:132 ms, 在所有 Python3 提交中击败了78.89%的用户
# 内存消耗:14.9 MB, 在所有 Python3 提交中击败了74.30%的用户
  • 复杂度分析
    • 时间复杂度:O(n^2)。
    • 空间复杂度:O(1)。

【动态规划法】

1、确定dp数组以及下标的含义:

  • dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是则dp[i][j]为true,否则为false。

2、确定递推公式:

  • 如果s[i]!=s[j],则dp[i][j]一定为False。
  • 如果s[i]==s[j]:
    • 如果i==j,即单个字符,则为True。
    • 如果 i 与 j 相差等于1,即两个字符,则为True。
    • 如果 i 与 j 相差大于1,而dp[i+1][j-1]是,则dp[i][j]也是,否则为False。

3、初始化时均为False。

4、res统计个数。

5、确认遍历顺序!:

  • 首先从递推公式中可以看出,情况三是根据dp[i + 1][j - 1]是否为true,在对dp[i][j]进行赋值true的。
    • dp[i + 1][j - 1] 在 dp[i][j]的左下角。所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的。

注意:因为dp[i][j]的定义,所以j一定是大于等于i的,那么在填充dp[i][j]的时候一定是只填充右上半部分。

借鉴该思路写出的代码:

class Solution:
    def countSubstrings(self, s: str) -> int:
        s=list(s)
        n=len(s)
        res=0
        dp=[[False]*n for i in range(n)]
        i=n-1
        while(i>=0):
            j=i
            while(j<n):
                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
                j+=1
            i-=1
        return res
# 执行用时:240 ms, 在所有 Python3 提交中击败了53.38%的用户
# 内存消耗:22.4 MB, 在所有 Python3 提交中击败了29.43%的用户

避坑之python初始化二维数组:

  • 初始化一个二维数组时,如果以方式multi = [[0] * 5] * 3,其实是不对的。因为[0] * 5是一个一维数组的对象,* 3的话只是把对象的引用复制了3次,此时修改了multi[0][0],却把multi[1][0],multi[2][0]也修改了!!!
  • 正确的方式应该是multilist = [[0] * 5 for row in range(3)],或者multilist = [[0 for col in range(5)] for row in range(3)]。


【部分内容参考自】

  • 回文子串:https://leetcode-cn.com/problems/palindromic-substrings/solution/hui-wen-zi-chuan-by-leetcode-solution/
  • 「代码随想录」647. 回文子串:【动态规划】【中心扩展】详解!:https://leetcode-cn.com/problems/palindromic-substrings/solution/647-hui-wen-zi-chuan-dong-tai-gui-hua-zh-6f7n/
  • python中初始化二维数组:https://blog.csdn.net/justheretobe/article/details/7632487
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值