不同子序列

LeetCode-Problem115-典型的二维动态规划问题,且可转一维

1)题目

在这里插入图片描述

2)分析
  • 初步思路两个子序列问题,通常都是二维及其以上的dp可解
  • 状态分析1:考虑二维dp,定义 s t a t e state state形式为 d p [ i ] [ j ] dp[i][j] dp[i][j],那么 d p [ i ] [ j ] dp[i][j] dp[i][j]的意义呢?dp[i][j]的极端的值(或者说端点)是 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]或者 d p [ n ] [ m ] dp[n][m] dp[n][m],其中 n 、 m n、m nm分别为 s 、 t s、t st长度;两个极端值有一个是我们要求的目标,具体是哪一个呢?
  • 状态分析2:这里哪个都可以,如果选取 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0],则我们定义 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 s [ i : n ] ( 左 闭 右 开 , 下 同 ) 、 t [ j : m ] s[i:n](左闭右开,下同)、t[j:m] s[i:n]t[j:m]的子序列关系,此时dp[0][0]表示s[0:n]含有多少个t[0:m]这样的子序列;选取 d p [ n ] [ m ] dp[n][m] dp[n][m]时, d p [ i ] [ j ] dp[i][j] dp[i][j]表示 s [ 0 : i ] s[0:i] s[0:i]含有多少个 t [ 0 : j ] t[0:j] t[0:j]子序列;两者只是方向不同,这里选择dp[0][0]作为目标;
  • 状态方程1:有了 s t a t e state state的形式和意义,接下来就是写出状态转移的方程;通常都是比较dp[i][j]和周边区间如dp[i+1][j]、dp[i][j+1]或者dp[i+1][j+1]的关系;这里针对子序列问题,考虑 s [ i ] 、 t [ j ] 是 否 相 同 s[i]、t[j]是否相同 s[i]t[j],可以写出如下状态方程:
    d p [ i ] [ j ] = { d p [ i + 1 ] [ j + 1 ] + d p [ i + 1 ] [ j ] if  s [ i ] = = t [ j ] d p [ i + 1 ] [ j ] if  s [ i ] ≠ t [ j ] dp[i][j] = \begin{cases} dp[i+1][j+1] +dp[i+1][j] &\text{if } s[i]==t[j] \\ dp[i+1][j] &\text{if } s[i] \ne t[j] \end{cases} dp[i][j]={dp[i+1][j+1]+dp[i+1][j]dp[i+1][j]if s[i]==t[j]if s[i]=t[j]
  • 状态方程2:上述方程表示,如果 s [ i ] = = t [ j ] s[i]==t[j] s[i]==t[j],如 s = " b b a g " s="bbag" s="bbag" t = " b a g " t="bag" t="bag" s [ 0 ] = t [ 0 ] = ‘ b ’ s[0]=t[0]=‘b’ s[0]=t[0]=b,此时 s [ 0 ] s[0] s[0]可以用来匹配 t [ 0 ] t[0] t[0],也可以放弃匹配,由 s [ 1 : ] s[1:] s[1:]等后续序列进行匹配;而如果 s [ i ] ≠ t [ j ] s[i] \ne t[j] s[i]=t[j],则可能的匹配子序列存在于 s [ i + 1 : ] 和 t [ j ] s[i+1:]和t[j] s[i+1:]t[j]之间;
  • 初始状态:上述状态方程的初始状态是 d p [ n ] [ − ] 和 d [ − ] [ m ] dp[n][-]和d[-][m] dp[n][]d[][m] i = n i=n i=n表示s="",此时 d p [ n ] [ − ] = 0 dp[n][-]=0 dp[n][]=0 j = m j=m j=m表示t="",此时 d p [ − ] [ m ] = 1 dp[-][m]=1 dp[][m]=1;注意dp[n][m]=1,代表s="“和t=”"也能匹配一次;
  • 举例:以s="babgbag"和t="bag"为例,状态方程初始化如下图@windliang在这里插入图片描述
  • 代码
class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        # 二维(or一维),并以t为dp数组
        # 动态方程如下
        # dp[i][j] = dp[i+1][j+1] + dp[i+1][j] or   # if s[i]==t[j]
        #            dp[i+1][j]                     # else

        dp = [[0 for _ in range(len(t)+1)] for _ in range(len(s)+1)]
        for i in range(len(s), -1, -1):  # 初始化dp[-][n]=1,注意dp[m][-]=0已经默认初始化了
            dp[i][len(t)] = 1
        for i in range(len(s)-1, -1, -1):
            for j in range(len(t)-1, -1, -1):
                if s[i] == t[j]:
                    dp[i][j] = dp[i+1][j+1] + dp[i+1][j]
                else:
                    dp[i][j] = dp[i+1][j]
        return dp[0][0]
  • 二维->一维:从动态方程可以看出, d p [ i ] [ j ] dp[i][j] dp[i][j]只跟临近一个step的状态有关系,如 d p [ i + 1 ] [ j + 1 ] dp[i+1][j+1] dp[i+1][j+1],这就为二维dp转变为一维dp提供条件,因为一维dp数组本身可以分更新前和更新后的值,天然的就可以区分一个step的变化;代码如下:
class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        # 二维->一维,并以t长度为dp数组长度
        # 动态方程如下
        # dp[i][j] = dp[i+1][j+1] + dp[i+1][j] or   # if s[i]==t[j]
        #            dp[i+1][j]                     # else
        dp = [0 for _ in range(len(t)+1)]
        dp[-1] = 1  # 边界条件,表示t="",则s有1个相应子序列
        for i in range(len(s)-1, -1, -1):  # s逆向
            for j in range(len(t)):  # t正向,以符合更新要求
                if s[i] == t[j]:  # 注意这里只需判断s[i]==t[j]的情况,不等的情况根据上述方程和dp的定义,dp[j]会保持不变,所以不需要更新,读者可仔细看一下这个巧合;
                    dp[j] += dp[j+1]
        return dp[0]
  • 总结熟悉两个序列关系计算的二维动态规划使用,本题属于区间型动态规划
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值