-
题目描述:
给你一个 不含重复 单词的字符串数组 words ,请你找出并返回 words 中的所有连接词 。
连接词 定义为:一个完全由给定数组中的至少两个较短单词组成的字符串。 -
示例:
输入:words = [“cat”,“cats”,“catsdogcats”,“dog”,“dogcatsdog”,“hippopotamuses”,“rat”,“ratcatdogcat”]
输出:[“catsdogcats”,“dogcatsdog”,“ratcatdogcat”]
解释:“catsdogcats” 由 “cats”, “dog” 和 “cats” 组成;
“dogcatsdog” 由 “dog”, “cats” 和 “dog” 组成;
“ratcatdogcat” 由 “rat”, “cat”, “dog” 和 “cat” 组成。
输入:words = [“cat”,“dog”,“catdog”]
输出:[“catdog”] -
解析:这个题目说难很难,说不难也不难。如果你深刻理解字典树的原理,那么这题就很简单,只要实现了一颗字典树,然后再加上一个简单的深度优先遍历,这个题目就解决了。如果不知道字典树,那么这个题目基本很难解出来。
首先说说字典树吧,字典树(Trie),就是一个以字母作为每个节点值的一种树,一条路径表示一个单词,如下所示。懂了下图,其实也就够了,现在我们来考虑怎么实现这个字典树。
-
图片来源:字典树(Trie)详解
-
第一步:节点的构成,假如我们所有的单词均由小写字母构成,那么从root节点开始,每个节点都有26个子节点,表示a-z,除了这个我们考虑到,一条路径就是一个单词,因此路径是有限的,我们还需要为节点指定一个状态,表示到这个节点,单词路径是否已经到了尽头。如对上图,对于路径a-b-c,构成了以个单词abc,那么c就应该有一个状态,指示到c节点,该单词路径结束,所以我们的Trie的构造函数就出来了:
def __init__(self):
self.children = [None]*26 #表示26个小写字母
self.isEnd = False #表示该节点是否是单词的最后一个字母
- 第二步:树的构成。我们再观察上图,给定一个单词"abcd",如何将其加入Trie呢?只要大家对二叉树有过了解,这里应该不难,就是一个简单的遍历+深度优先赋值,这里我们用ord© - ord(‘a’)的值来表示该字母c对应的哪一个节点,即self.children[0]表示"a",self.children[1]表示"b",以此类推。实现代码如下:
def insert(self, word):
node = self #self表示根节点root
for c in word:
cV = ord(c) - ord('a')
if not node.children[cV]:
node.children[cV] = Trie()
node = node.children[cV]
node.isEnd = True
- 第三步:判断单词是否由该数的单词组合而成。我们可以这么考虑,假如我们的树已经有了cat、dog两个单词路径,判断catdog是不是该树的单词产生,那么我们可以将catdog分解成两个部分,即cat和dog,并给定一个中间变量temp记录已经遍历过的字符,如果node.isEnd = True 同时temp=len(word)那么就说明该word由Trie树中的单词组成。换句话说,就是一个单词路径走完,再回到根节点走下一条单词路径,直到顺利走完整个word,这个时候才能返回True,其他情况全部是False,代码如下:
def dfs(self, word, temp):
if temp == len(word):
return True
node = self
for i in range(temp, len(word)):
node = node.children[ord(word[i]) - ord('a')]
if not node:
return False
if node.isEnd and self.dfs(word, i+1):
return True
return False
- 我们再回到本题,给你一个不含重复 单词的字符串数组 words ,请你找出并返回 words 中的所有连接词。由于本题不含重复单词,我们考虑到只有“长”单词才有可能是组合词,那么这个“长”如何定义呢?答案就是对words字符串数组,按照长度进行排序即可,因为如果该单词是组合词,那么他一定是由在他之前的单词组合而成。这里可以采用反证法证明,如果组成该单词a的某一个单词b,在该单词之后,那么b的长度一定大于a,如果b的长度大于a,那么b一定不可能是a的子词。所以,基于字典树的解题思路就有了,当该字符是组合词的时候,记录,如果不是组合词,将该词添加进字典树,所有代码如下:
class Trie:
def __init__(self):
self.children = [None]*26 #表示26个小写字母
self.isEnd = False #表示该节点是否是单词的最后一个字母
def insert(self, word):
node = self #self表示根节点root
for c in word:
cV = ord(c) - ord('a')
if not node.children[cV]:
node.children[cV] = Trie()
node = node.children[cV]
node.isEnd = True
def dfs(self, word, temp):
if temp == len(word):
return True
node = self
for i in range(temp, len(word)):
node = node.children[ord(word[i]) - ord('a')]
if not node:
return False
if node.isEnd and self.dfs(word, i+1):
return True
return False
class Solution:
def findAllConcatenatedWordsInADict(self, words: List[str]) -> List[str]:
"""
给你一个 不含重复 单词的字符串数组 words ,请你找出并返回 words 中的所有连
接词
>>>words = ["cat","dog","catdog"]
>>>self.findAllConcatenatedWordsInADict(words)
>>>"catdog"]
"""
words.sort(key = len)
ans = []
root = Trie()
for word in words:
if word == "":
continue
if root.dfs(word, 0):
ans.append(word)
else:
root.insert(word)
return ans
- 如果大家觉得有帮助,欢迎点个免费的赞,谢谢!