leetcode | 115. Distinct Subsequences

题目

Given a string S and a string T, count the number of distinct subsequences of S which equals T.

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, “ACE” is a subsequence of “ABCDE” while “AEC” is not).

Example 1:

Input: S = "rabbbit", T = "rabbit"
Output: 3

Explanation:

As shown below, there are 3 ways you can generate "rabbit" from S.
(The caret symbol ^ means the chosen letters)

rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^

Example 2:

Input: S = "babgbag", T = "bag"
Output: 5

Explanation:

As shown below, there are 5 ways you can generate "bag" from S.
(The caret symbol ^ means the chosen letters)

babgbag
^^ ^
babgbag
^^    ^
babgbag
^    ^^
babgbag
  ^  ^^
babgbag
    ^^^

思路与解法

这道题题意是让我们从字符串S中删除一些字符,从而使得剩下的字符在不改变相对位置的前提下构成字符串T。我们需要求出的所有满足上述条件的方法数。
起初,可能并没有什么想法,因为字符串S中的字符位置不固定,并且相同的字符可能出现多次,使用一般的暴力搜索解决不了该问题。所以我们应该转变一下思想:考虑我们现在需要将字符串S[:i](不包含第i个字符,下同)经过删除一些字符之后转化为字符串T[:j],那么

  • 假如S[i-1]==T[j-1],则此时的方法数为①将字符串S[:i-1]经过删除一些字符之后转化为字符串T[:j];②将字符串S[:i-1]经过删除一些字符之后转化字符串T[:j-1]。前者表示将S[i]去除,后者表示不去除S[i]
  • 假如S[i-1]!=T[j-1],则此时的方法数为:将字符串S[:i-1]经过删除一些字符之后转化为字符串T[:j]

到此处,我们可以使用动态规划来解决此问题,定义数据如下:dp[i][j]表示将S[:i]经过删除一些字符之后转发为T[:j]的方法数。
状态转移方程如下:

// s[i-1]表示的是S的第i个字符
if s[i-1] == t[j-1] {
    dp[i][j] = dp[i-1][j] + dp[i-1][j-1]
} else {
    dp[i][j] = dp[i-1][j]
}

代码实现(Go)

func numDistinct(s string, t string) int {
    lenS := len(s)
    lenT := len(t)
    dp := make([][]int, 0)
    // 初始化dp数组,dp[i][0]表示将S[:i]转化为空串的方法数:1
    for i:=0; i<=lenS; i++ {
        dp = append(dp, make([]int, lenT+1))
        dp[i][0]=1
    }
	// dp[0][j]表示将空串转化为T[:j]的方法数
    for j:=1; j<=lenT; j++ {
        dp[0][j]=0
    }
	// 状态转移
    for i:=1; i<=lenS; i++ {
        for j:=1; j<=lenT; j++ {
            if s[i-1] == t[j-1] {
                dp[i][j] = dp[i-1][j] + dp[i-1][j-1]
            } else {
                dp[i][j] = dp[i-1][j]
            }
        }
    }

    return dp[lenS][lenT]
}

测试结果

在这里插入图片描述

算法改进

上述算法的时间复杂度为O(N*M),控件复杂度也为O(N*M)。我们可以改进空间复杂度:
由于我们的状态转移方程中dp[i]都是与dp[i-1]相关,所以我们可以省掉维持i的一维空间。但是我们需要做一些修改:内层循环需要反序!!
原因如下:因为dp[i][j]需要dp[i-1][j]的值,即我们的第i次循环需要i-1循环所记录的值。如果我们的内层循环依然正序,则在第i次循环时,dp[i-1][j]的值便被覆盖为dp[i][j],使得当我们计算dp[i][j+1]需要用到dp[i-1][j]时出现错误。

func numDistinct(s string, t string) int {
    lenS := len(s)
    lenT := len(t)
    dp := make([]int, lenT+ 1)
    dp[0] = 1
    for i := 1; i <= lenS ; i++ {
        for j := lenT ; j >= 1; j-- {
            if s[i - 1] == t[j - 1] {
                dp[j] += dp[j-1]
            }
        }
    }
    return dp[lenT]
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值