【python】Leetcode每日一题-不同的子序列
【python】Leetcode每日一题-不同的子序列
【题目描述】
给定一个字符串 s
和一个字符串 t
,计算在 s
的子序列中 t
出现的个数。
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE"
是 "ABCDE"
的一个子序列,而 "AEC"
不是)
题目数据保证答案符合 32
位带符号整数范围。
示例1:
输入:s = "rabbbit", t = "rabbit"
输出:3
解释:
如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
(上箭头符号 ^ 表示选取的字母)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
示例2:
输入:s = "babgbag", t = "bag"
输出:5
解释:
如下图所示, 有 5 种可以从 s 中得到 "bag" 的方案。
(上箭头符号 ^ 表示选取的字母)
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^
提示:
0 <= s.length, t.length <= 1000
s 和 t 由英文字母组成
【分析】
-
【超时】思路:
自己原先思路是对
s
和t
顺序匹配,使用dfs
遍历,最后时间超时,分析时间复杂度,代码模型为\(O(m^n)\),当然比这个小很多,并不是完整的树结构。代码:
class Solution(object): s = "" t = "" num = 0 def numDistinct(self, s, t): """ :type s: str :type t: str :rtype: int """ self.s = s self.t = t self.dfs(0, 0) return self.num def dfs(self, s_index, t_index): if(t_index == len(self.t)): self.num += 1 return for x in range(len(self.s)-s_index): if(self.s[x+s_index] == self.t[t_index]): self.dfs(x+s_index+1, t_index+1)
-
【dp】思路:
-
dp[i][j]
表示s[0...i]
、t[0...j]
之间的子序列数。 -
提供dp转移方程:
- 解释:当
s[i] == t[j]
时,dp[i][j]
可分为s
串尾部是否形成匹配两种情况,如果在与t匹配中形成匹配了,则为子序列为dp[i-1][j-1]
,即除开s
、t
两串最后一个字母的子序列数,如果未形成匹配,则为dp[i-1][j]
,即s
、t
两串不包含s串最后一个字母的子序列数。 - 时间复杂度:\(O(mn)\)
AC代码:
class Solution(object): def numDistinct(self, s, t): """ :type s: str :type t: str :rtype: int """ m = len(s) n = len(t) dp = [[0]*(n+1) for i in range(m+1)] for i in range(m+1): dp[i][0] = 1 for i in range(1, m+1): for j in range(1, n+1): if(j > i): continue if(s[i-1] == t[j-1]): dp[i][j] = dp[i-1][j-1] + dp[i-1][j] else: dp[i][j] = dp[i-1][j] return dp[m][n]
-
-
讨论:
-
看到一位兄弟的评论,图一乐??
去?的超时!不过63个测试用例而已,大不了我写63个case
public class Solution { public int NumDistinct(string s, string t) { switch(t){ case "bddabdcae":return 10582116; case "bcddceeeebecbc":return 700531452; case "ccdeddeabb":return 527764581; case "baaacbceabba":return 1293119; case "aeacbde":return 2543265; case "rwmimatmhydhbujebqehjprrwfkoebcxxqfktayaaeheys":return 543744000; case "rwmimatmhydhbujebqehjprarwfkoebcxxqfktayaaeheys":return 1456742400; default:return fun(s,t,0,0); } } private int fun(string s,string t,int i,int j){ int res=0; for(;i
-
看了官方题解,意识到动态规划边界条件的确也很重要:
-
当
j=n
时,t[j:]
为空字符串,由于空字符串是任何字符串的子序列,因此对任意 \(0 \le i \le m\),有 \(\textit{dp}[i][n]=1\); -
当
i=m
且j
时, s[i:]
为空字符串,t[j:]
为非空字符串,由于非空字符串不是空字符串的子序列,因此对任意 \(0 \le j,有 \(\textit{dp}[m][j]=0\)。
-
-
官方题解: 戳这里
-