【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 由英文字母组成

【分析】

  • 【超时】思路

    自己原先思路是对 st 顺序匹配,使用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转移方程:

    \[\begin{cases} dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];&s[i] == t[j]\\ dp[i][j] = dp[i - 1][j];&s[i] != t[j] \end{cases} \]

    • 解释:当s[i] == t[j]时,dp[i][j]可分为s串尾部是否形成匹配两种情况,如果在与t匹配中形成匹配了,则为子序列为dp[i-1][j-1],即除开st两串最后一个字母的子序列数,如果未形成匹配,则为dp[i-1][j],即st两串不包含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=mj 时,s[i:] 为空字符串,t[j:] 为非空字符串,由于非空字符串不是空字符串的子序列,因此对任意 \(0 \le j,有 \(\textit{dp}[m][j]=0\)

    • 官方题解: 戳这里