leetcode 792. Number of Matching Subsequences
题目描述
Given string S
and a dictionary of words words
, find the number of words[i]
that is a subsequence of S
.
Note:
- All words in
words
andS
will only consists of lowercase letters.. - The length of
S
will be in the range of[1, 50000]
. - The length of
words
will be in the range of[1, 5000]
. - The length of
words[i]
will be in the range of[1, 50]
.
Difficulty: Medium
792. Number of Matching Subsequences
中文描述
给你一个字符串S
和一些单词组合words
。找出所有words
中可以由S
子串构成的单词word
的个数。
输入格式
输入个字符串S
和一些单词组合words
。
Examples:
- Input: S = “abcde”,words = [“a”, “bb”, “acd”, “ace”]
Output: 3
解释:
a, acd, ace可以由S = “abcde”的子串组成(不用连续),所以答案为3
解答思路
解法一:逐个比对
1.把 words w o r d s 里的单词 word w o r d 和 S S 逐个比对。用两个指针分别记录 和 S S 当前的位置,如果匹配两个指针都往前进1,否则只有 的指针前进。如果 S S 的指针先到尾部表示无法由子串构成,反正表示可以。
2.复杂度估计为 words w o r d s 的个数, n n 表示 的长度。这种情况会超时。
解法二:二分查找
1.考虑第一种情况主要是因为字符批对的效率不够高,我们需要一位位字符批对过去。所以我们可考虑用更高效的方法去批对。
2.先记录下 S S 中各个字符出现的位置,并且按顺序记录下来,记为 。
3.初始化记录当前指向 S S 位置的指针为-1,对每个 的字符 char c h a r 去 dicts d i c t s 中查找是否存在该字符,如果没有则说明子串不存在,可以跳转下一个 word w o r d 。反之,我们可以用二分查找的方式去寻找 S S 中所有字符 的位置 中,第一个大于当前指向 S S 位置的指针的位置(这边用到了贪心的思想)。如果存在,则更新指向 位置的指针到该位置。否则说明子串不存在。
4.复杂度估计 O(mlogn),m O ( m l o g n ) , m 为 words w o r d s 的个数, n n 表示 的长度。
解法三:字典树
1.把 words w o r d s 里中具有相同开头的单词 word w o r d 开头字符合并,记成一个字典树。
2.遍历 S S 中的每个字符 ,如果 char c h a r 存在当前字典树中,则把该节点删除,把原本该节点的元素根据剩下字符串的开头字符重新分配,构成新的字典树。如果剩下为空字符,表示该字符串能由 S S 的子串构成,计数器加1。
3.当遍历完 ,返回计数器的的结果即是最后答案。
4.复杂度估计 O(nk),n O ( n k ) , n 为 S S 的长度, 表示删除一个节点后,需要重新分配字符串的平均个数。最坏情况下 k=m k = m , m m 为 的个数。不过常规情况下 k<<m k << m 。
代码
解法一,超时
class Solution(object):
def numMatchingSubseq(self, S, words):
"""
:type S: str
:type words: List[str]
:rtype: int
OVERTIME
"""
ans = 0
for word in words:
start = 0
i = 0
# word的每个字符去查找
while i < len(word):
# 与S字符一个个比对
while start < len(S):
# 有相同的,双方都往前进1位
if word[i] == S[start]:
start += 1
i += 1
break
# 否则只有S往前进一位
else:
start += 1
# 如果S全部走完,还是不能与word匹配,则跳出
if start >= len(S):
break
# 记录所有能从S中找到的word
if i == len(word):
ans += 1
return ans
解法二,二分:
class Solution(object):
def numMatchingSubseq(self, S, words):
"""
:type S: str
:type words: List[str]
:rtype: int
938MS
"""
import collections, bisect
ans = 0
S_dicts = collections.defaultdict(list)
# 记录下S中每个字母出现的位置
for i, char in enumerate(S):
S_dicts[char].append(i)
for word in words:
# 一开每个从S的0位开始,用二分查找,我们就从-1开始
start = -1
flag = True
# word中的每个字母去S中查找位置
for char in word:
# 如果该字母不再S中,则word不能由S子串组成
if S_dicts[char] == []:
flag = False
break
else:
# 在S中查找最近的位置,且要在上次选定的位置之后的
pos = bisect.bisect(S_dicts[char], start)
# 如果有,则更新当前位置
if pos < len(S_dicts[char]):
start = S_dicts[char][pos]
# 找不到满足条件的位置
else:
flag = False
# 满足条件的word,ans+1
if flag:
ans += 1
return ans
解法三,字典树,参考了别人的代码:
class Solution(object):
def numMatchingSubseq(self, S, words):
"""
:type S: str
:type words: List[str]
:rtype: int
527ms
"""
import collections
waiting = collections.defaultdict(list)
for w in words:
waiting[w[0]].append(iter(w[1:]))
for c in S:
# print('c', c)
# Python 字典 pop() 方法删除字典给定键 key 所对应的值,返回值为被删除的值。key值必须给出。 否则,返回default值。
# 把所有以c开头的word都删除
for it in waiting.pop(c, ()):
# 如果这个word还有其他字母,则与之前的合并,否则放到None中,表示该word能匹配
waiting[next(it, None)].append(it)
return len(waiting[None])