问题

给出字符串S和T,计算S中为T的不同的子序列的个数。

一个字符串的子序列是一个由该原始字符串通过删除一些字母(也可以不删)但是不改变剩下字母的相对顺序产生的一个新字符串。如,ACE是ABCDE的一个子序列,但是AEC不是。

这里有一个例子:

S=“rabbbit”,T=“rabbit”

返回值应为3

初始思路

要找出子序列的个数,首先要有找出S中为T的子序列的方法。T是S的子序列,首先其每一个字母肯定会在S中出现,通过遍历T的每一个字母即可完成这个检查。而根据不能乱序的要求,下一个字母在S中出现的位置不能在上一个字母在S中出现的位置之前。由此,我们得到下面的算法:

循环遍历T

   如果当前字母在S中,而且在S中的位置大于前一个字母在S中的位置

      继续循环

   否则

      返回

循环结束

确认T为S的子序列

上面的算法用来找S中存不存在唯一的T子序列没有问题,但是如果T中的字母在S中出现多次就不靠谱了。当T中字母多次出现在S时,意味着出现了分支。如S:doggy,T:dog。当我们遍历到g字母时,其实出现了取S中两个不同g字母的分支。看到分支,我们可以想到递归:把循环遍历T的过程改为递归,每次递归调用要处理的T的位置加1,递归结束条件为走到T的结尾。经过这样变化,每次递归条件达成意味着一个子序列出现,这样也达到了我们计算子序列个数的目的。

查找子序列(T,要查找的字母在T中的位置,上一个字母在S中的位置)

   如果 要查找的字母在T中的位置 > T的长度

      子序列个数加1

      返回

   如果当前字母在S中

      循环遍历S中所有该字母的位置

         如果当前位置 <=  上一个字母在S中的位置

            继续循环

         查找子序列(T,要查找的字母在T中的位置 + 1, 当前位置)

在上面的伪代码中,我们发现判断当前字母是否在S中并获取它在S中的位置这个功能将会被频繁调用。在具体实现时,我们应该联想到使用关联容器(如map)这种查找速度比较快的数据结构(用以字母为下标的数组也可以,查找速度更快。但是需要考虑大小写字母,非英文字母等情况)。字母可以作为关联容器的key,而一个存放位置信息的序列容器(如vector)可以作为关联容器的值。在进行正式计算前,先遍历S生成这个存放信息的关联容器,这样以后我们就不再需要S本身了。最后得到代码如下:

 class Solution {
public:
int numDistinct(std::string S, std::string T)
{
if(T.size() >= S.size())
{
if(S == T)
{
return ;
}
else
{
return ;
}
} positionInfo_.clear();
count_ = ; for(int i = ; i < S.size(); ++i)
{
if(positionInfo_.find(S[i]) == positionInfo_.end())
{
positionInfo_[S[i]] = {i};
}
else
{
positionInfo_[S[i]].push_back(i);
}
} FindDistinct(T, , -); return count_;
} private: void FindDistinct(std::string& T, int pos, int previousPosInS)
{
if(pos > T.size() - )
{
++count_;
return;
} const auto iter = positionInfo_.find(T[pos]); for(auto posIter = iter->second.begin(); posIter != iter->second.end(); ++posIter)
{
if(*posIter <= previousPosInS)
{
continue;
} FindDistinct(T, pos + , *posIter);
}
} std::map<char, std::vector<int>> positionInfo_; int count_;
};

numDistinct

提交后Judge Small顺利通过,但是Judge Large超时了。

优化

针对递归计算的优化方法,通过以前题目的分析我们应该比较有经验了:无非就是通过缓存计算结果避免在递归分支中的重复计算。让我们用例子中的S和T来看看递归过程:

可以看到从T的pos为4的地方存在重复计算,由rab1b2已经可以知道取i后只有1种子序列了。这样看起来似乎可以用T的pos作为key,在rab1b2的递归序列中纪录count[4]=1。随后在rab1b3的递归中到达pos3层后不用继续递归pos4层即可查表得到本次的子序列数1,看起来似乎没有问题。但是当我们回到pos为2的rab1时,就可以发现隐藏的错误了,此时我们记录count[3]=2。紧接着我们开始处理pos2层的rab2的递归,经过查表得到子序列个数为count[3]=2。这显然是错误的,rab2继续递归并没有两种子序列。

分析一下错误的原因,我们发现其实某点开始的子序列的个数不但和当时T的位置有关,还和当时在S中选取的字母在S中的位置有关。因此处理完rab1递归时我们的缓存应该为count[2, 2] = 2(2为第一个b在S中的位置)。这样在处理rab2时,[2,3]是没有缓存的,我们通过递归可以得到正确的值1。而前面提到的count[4]=1的缓存变为count[4, 5]=1(5为i在S中的位置),不会影响结果。

现在可以开始实现代码了,由于需要缓存数据,我们得在原来基础上做一些小修改,不再使用成员变量纪录子序列个数,而是使用返回值。这样子才有办法缓存不同递归序列中的中间结果。至于缓存,使用一个std::map<std::pair<int, int>, int>即可。完成后的代码如下:

 class SolutionV3 {
public:
int numDistinct(std::string S, std::string T)
{
if(T.size() >= S.size())
{
if(S == T)
{
return ;
}
else
{
return ;
}
} positionInfo_.clear();
cachedResult_.clear(); for(int i = ; i < S.size(); ++i)
{
if(positionInfo_.find(S[i]) == positionInfo_.end())
{
positionInfo_[S[i]] = {i};
}
else
{
positionInfo_[S[i]].push_back(i);
}
} return FindDistinct(T, , -);
} private: int FindDistinct(std::string& T, int pos, int posInS)
{
if(pos > T.size() - )
{
return ;
} int count = ;
int result = ; const auto iter = positionInfo_.find(T[pos]); for(auto posIter = iter->second.begin(); posIter != iter->second.end(); ++posIter)
{
if(*posIter <= posInS)
{
continue;
} CacheKey cacheKey(pos, *posIter); if(cachedResult_.find(cacheKey) != cachedResult_.end())
{
count += cachedResult_[cacheKey];
continue;
} result = FindDistinct(T, pos + , *posIter);
cachedResult_[cacheKey] = result;
count += result;
} return count;
} std::map<char, std::vector<int>> positionInfo_; std::map<std::pair<int, int>, int> cachedResult_; typedef std::pair<int, int> CacheKey;
};

numDistinct_cached

顺利通过Judge Large。

[LeetCode 115] - 不同子序列(Distinct Subsequences)的更多相关文章

  1. leetcode@ [72/115] Edit Distance & Distinct Subsequences (Dynamic Programming)

    https://leetcode.com/problems/edit-distance/ Given two words word1 and word2, find the minimum numbe ...

  2. [Swift]LeetCode115. 不同的子序列 | Distinct Subsequences

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

  3. LeetCode之“动态规划”:Distinct Subsequences

    题目链接 题目要求: Given a string S and a string T, count the number of distinct subsequences of T in S. A s ...

  4. 不同的子序列 · Distinct Subsequences

    [抄题]: 给出字符串S和字符串T,计算S的不同的子序列中T出现的个数. 子序列字符串是原始字符串通过删除一些(或零个)产生的一个新的字符串,并且对剩下的字符的相对位置没有影响.(比如,“ACE”是“ ...

  5. [LeetCode] 115. Distinct Subsequences 不同的子序列

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

  6. [leetcode]115. Distinct Subsequences 计算不同子序列个数

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

  7. [LeetCode] Distinct Subsequences 不同的子序列

    Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequence ...

  8. 子序列 sub sequence问题,例:最长公共子序列,[LeetCode] Distinct Subsequences(求子序列个数)

    引言 子序列和子字符串或者连续子集的不同之处在于,子序列不需要是原序列上连续的值. 对于子序列的题目,大多数需要用到DP的思想,因此,状态转移是关键. 这里摘录两个常见子序列问题及其解法. 例题1, ...

  9. 【LeetCode】115. Distinct Subsequences 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 动态规划 日期 题目地址:https://leetc ...

随机推荐

  1. L - Cat VS Dog - HDU 3829(最大独立集)

    题意:有P个孩子,有的孩子喜欢猫不喜欢狗,有的喜欢狗不喜欢猫(喜欢的和不喜欢的一定是相相对立的动物),动物园有N只猫,M只狗,每个孩子都有喜欢的猫讨厌的狗(或者喜欢的狗讨厌的猫),现在动物园要送走一批 ...

  2. Linux2.6内核--VFS层中和进程相关的数据结构

          系统中的每一个进程都有自己的一组打开的文件,像根文件系统,当前工作目录,安装点等.有三个数据结构将 VFS 层和系统的进程紧密的联系在一起,它们分别是: file_struct,fs_st ...

  3. linux下ssh使用和配置

    常用格式:ssh [-l login_name] [-p port] [user@]hostname 更详细的可以用ssh -h查看. 举例 不指定用户: ssh 192.168.0.11 指定用户: ...

  4. sql server 2005中IMAGE类型的BUG问题

    目的:在sql server 2005数据库上筛选出那些有照片的员工 由于客户之前的数据库是sql server 2000,定义的photo字段的数据类型为image, 在sql 2005数据库上,用 ...

  5. appcan里面模板的使用

    1:首先要定义一个字符串如果太长需要换行,可以用"\"来分割每行 2:模板里面使用的是ejs语法,所以可以使用if else语句等 3:字符串定义好之后要用appcan.view. ...

  6. Object -C NSNumber -- 笔记

    // //  main.m //  NSNumber // //  Created by facial on 24/8/15. //  Copyright (c) 2015 facial_huo. A ...

  7. Android 6.0 反射 newInstance

    Android 6.0 从预览版发布到现在已经快一年的时间了,然而市场占有率还是低的可怜,国内更不用说.随着三星.华为.小米开始升级至6.0 ,以及上半年各厂商新机逐渐发布,6.0的市场占有率必将迎来 ...

  8. SKShapeNode类

    继承自 SKNode:UIResponder:NSObject 符合 NSCoding(SKNode)NSCopying(SKNode)NSObject(NSObject) 框架  /System/L ...

  9. Android Monkey具体解释

    Monkey是一个执行在Android设备或者虚拟机上的程序,它能随机模拟一系列用户的操作.点击,滑动,能够用它来做压力測试. 在Monkey执行的过程中,它会产生并发送一系列的事件给系统,而且监听系 ...

  10. 零基础学习IOS开发(二)- 使用cocos2d-x3.0 执行Hello world

    关于开发框架,依据网上检索来的信息,感觉cocos2d-x的ios游戏开发框架非常不错,并且有非常强的可移植性,因此打算尝试一下. 截止写下此文章,最新的cocos2d-x的版本号为v3.0稳定版(几 ...