技术提高是一个循序渐进的过程,所以我讲的leetcode算法题从最简单的level开始写的,然后到中级难度,最后到hard难度全部完。目前我选择C语言,Python和Java作为实现语言,因为这三种语言还是比较典型的。由于篇幅和精力有限,其他语言的实现有兴趣的朋友请自己尝试。
如果有任何问题可以在文章后评论或者私信给我。
如果有朋友希望我讲些其他话题,请在评论区留言或者私信给我。
持续分享,敬请关注。
LeetCode 392 的扩展题
问题描述:
给定两个字符串 s 和 t ,判断 s 是否为 t 的子序列。假设 s 和 t 仅包含英文小写字母。字符串 t 可能会是一个非常长的字符串(长度可以达到500,000),而 s 是个比较短的字符串(长度 <=100)。
字符串的子序列是指通过删除一些(也可不删除)原始字符串中的某些字符,但是不会调整剩余字符的相对位置,这样形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
如果有很多输入 S,记作S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。
C语言实现:
在《LeetCode 392》中,算法的复杂是O(len(t))。而t的长度可能会非常的大,达到50万,那么如果有10亿个s的输入,这个时间开销是很大的。
我们要想到一种解决办法,让整个算法的复杂度依赖s,而不是t,毕竟s的长度最大只有100,这样要快的多。
我的实现是先根据t建立一个大小为26*100的二维表 map,用来记录t中每个字母的位置分布信息。
其中26对应26个小写字母;
而100是因为,对于每个s,长度不会超过100, 所以如果s仅仅由一个字母组成,那么这个字母最多也仅仅会出现100次,所以如果t中某个字母出现超过100次,那么后面再出现的话,这个新的位置信息可以忽略掉。
当我们建立好map这个表以后,我们就可以遍历每一个s。用s的每个字符c依次查询map表,获取字符c在t中出现的位置。
正如《LeetCode 392》所说的,如果s是t的子序列,那么s中每个字符c的下标应该是绝对递增的。我们正是依据这一点来确定s是不是t的子序列。
这种查表的时间复杂度就是O(len(s))。
下面是这个算法的示意图,后面在详细描述代码的时候,请记得看看回头这张图:
详细代码如下:
我们将map定义为全局变量,这是因为,当map没建立(初始化)的时候,我们用下面定义的函数initMap完成这个工作后,后面调用isSubsequence检测s是否是t子序列的时候,就可以直接使用了,无需再做了。
变量hasInit是用来检测map是否已经建立完成,如果没有就调用initMap来做。
在initMap函数中,定义一个长度为26的整型数组idx,用来在遍历t的时候,记录某个字母出现了几次;我们通过t[i] - 'a'来定位字母t[i]的位置信息在map中存储位置。同样可以找到idx中该字母记录的个数,如果idx中的记录表明,这个字母已经出现100了,那么就无须再记录位置信息了,继续下一个字母,否则我们就追加一个位置信息到map中。
注意,这里我们记录的位置信息是从1开始的,即位置信息比下标大1,这是因为,下标的其实值为0,而map表中,所有元素的初值也是0,如果下标作为位置信息,有时候无法判断一个字母是否在t中存在,试想,如果s="k", t="abc"的例子。
isSubsequence函数中:
- 第一句在map没有建立的时候,调用initMap来创建。
- 这里idx和initMap中的idx作用是一样的,只是这里是为s服务的。
- 定义pre作为s中前一个字母的位置信息(如上所诉,位置信息是从1开始的),初始是0;然后遍历s:我们检查当前字母的位置信息是否比上一个字母的位置信息(存在pre中)大,如果不是,就说明s不是t的子序列,直接返回false,否则将当前字母的位置信息赋值给pre,继续下一轮比较。