【leetCode】115.不同的子序列

131 阅读3分钟

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

115.不同的子序列

原题目:

115. 不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)

暴力解法

对于这个题目,最简单的方法就是:

  • 我们只考虑第s第M个字符和T的第N个字符匹配的情况:

    • 如果匹配了,我们就开一个递归接下去考虑第M+1个字符和第N+1个字符匹配的情况
    • 然后不管匹配与否,在本方法中,考虑第M+1个字符和N的匹配情况
    • 边界条件就是,如果N+1越界了,那么就代表我们匹配结束了

这样,我们就可以把所有情况都考虑到。

那么,我们可以通过递归写出以下暴力代码:

 public int numDistinct(String s, String t) {
     return numDistinctFrom(s,t,0,0);
 }
 ​
 public int numDistinctFrom(String s,String t,int nxtIdx,int cur){
     if(nxtIdx>t.length()-1) return 1;
     if(cur>s.length()-1) return 0;
     int res = 0;
     for (int i = cur; i < s.length(); i++) {
         if(s.charAt(i)==t.charAt(nxtIdx)) res+= numDistinctFrom(s,t,nxtIdx+1,i+1);
     }
     return res;
 }

结果:超时

做匹配情况的记录

我们深入分析下s和t的匹配情况:

  • 如果发生了匹配,我们可以从任何位置和t的第一个字符匹配的地方开始;更换匹配结果和匹配开始位置,这个结论都是成立的
  • 任何不同的选择都让结果+1

其实我们就可以这样认为:

  • 如果到S的第M个字符,和t的第N个字符匹配,且在M之前和t的第N-1个字符匹配的不同情况有w个,那么:

    • 到S的第M个字符为止,和T的第N个字符匹配的不同情况,也为W个

    • 例如:

       假设:我们到第m个字符可以匹配1个字符的是2次,2个字符的是1次
       那么到第m+1个字符,如果m+1=t(2),此时可以匹配第二个字符的有(2+自身匹配)=3种可能,以此类推
      

那么我们就可以根据这个推论,来设计算法:

  • 顺序遍历S字符串,并记录到遍历节点为止,和t到第n个字符为止的匹配次数为a[n]。

  • 我们维护一个当前匹配最大字符数的值为peek且peek<t字符串长度,方便我们在匹配未结束之前减小我们的计算次数

  • 每一次遍历中假设S的字符为M,我们只需要判断t在[0,peek]之间的n,M是否等于t(n)

  • 如果发生了相等,就意味着:

    • S从0到到M-1,和取t的前n-1个字符比对,可以有a[n-1]种方法搞到相同的子串,此时又发生了匹配,那么意味着,此时a[n]就多了a[n-1]种选择。

最后,我们返回a[t.length]即可。

把上面的逻辑写成代码如下:

 public int numDistinct2(String s, String t) {
     int[] n = new int[t.length()+1];
     int peek = 0;
     //初始化
     n[0] = 1;
     for (int i = 0; i < s.length(); i++) {
         //这里需要倒着来防止重复计算
         for (int i1 = peek; i1 >= 0; i1--) {
             if(s.charAt(i) == t.charAt(i1)){
                 n[i1+1]+=n[i1];
             }
         }
         if(peek<t.length()-1 && n[peek+1]!=0){
             peek++;
         }
     }
     return n[t.length()];
 }

结果:

执行用时:6 ms, 在所有 Java 提交中击败了92.92%的用户

内存消耗:36.6 MB, 在所有 Java 提交中击败了93.87%的用户

和官方的题解相比,只用了一维数组来记录计算结果,因此内存消耗要更低一些。