LeetCode472 - Concatenated Words - Hard (Python)

xinhao 2020-01-31

Given a list of words (without duplicates), please write a program that returns all concatenated words in the given list of words.

A concatenated word is defined as a string that is comprised entirely of at least two shorter words in the given array.

Example:

Input: ["cat","cats","catsdogcats","dog","dogcatsdog","hippopotamuses","rat","ratcatdogcat"]

Output: ["catsdogcats","dogcatsdog","ratcatdogcat"]

Explanation: "catsdogcats" can be concatenated by "cats", "dog" and "cats";  "dogcatsdog" can be concatenated by "dog", "cats" and "dog"; "ratcatdogcat" can be concatenated by "rat", "cat", "dog" and "cat".题意:在一个不重复的list中找到,找到一类单词,这个单词可以由list中的其他word来组成。在组成的过程中,每一个单词可以重复使用。思路:这题和word ladder有点像,都是进行单词的拼接。方法一:dfs对每一个单词进行dfs搜索。如果满足dfs搜索,那么在最终的res中加入这个单词。需要注意的是在dfs前回需要将word从word list中删掉,不然在后续dfs的判断中,会出错。等搜索完,再把这个单词加到word list中dfs的函数为def check(self, word, word_set)。 dfs跳出的判断条件是 if word in word_set, return True.(注意这也是为什么再最开始进入dfs循环前,我们会需要把word从word list中删掉的原因。)用一个指针i在word中进行遍历(指针的遍历范围为(1,len(word)+1),如果word[:i]在word_set中word[i:],我们对word[i:]进行dfs的判断。如果word[i:]的dfs搜索也返回的是True,那整个word我们就可以返回True时间复杂度:O(2^l*n)l是最长的单词的长度,n是单词的个数。
class Solution:
    def findAllConcatenatedWordsInADict(self, words: List[str]) -> List[str]:
        res = []
        word_set = set(words)
        
        for word in word_set:
            word_set.remove(word)
            if self.check(word, word_set):
                res.append(word)
                
            word_set.add(word)
            
        return res 
    
    def check(self, word, word_set):
        if word in word_set:
            return True 
        for i in range(1, len(word)+1):
            if word[:i] in word_set and self.check(word[i:], word_set):
                return True 
        return False

方法二:Trie + dfs (Trie的固定code还是不太会写) 

步骤:1 完成TrieNode()  Trie()的类的构造

         2 dfs的函数是 def dfs(self, node, word, i, space)。i指的是指针遍历在word的位置, space指的在这个单词之前是否有匹配完成另一个单词,即为两个拼接的单词中间的space。对于dfs函数来说,跳出的判断条件是i == len(word). 如果满足这个要求了,那我们返回的是return node.isEnd and space. (如果单纯只是node.isEnd是不够的,因为我们需要两个及以上的单词拼接完成一个单词,即我们肯定需要有一个space)。如果不满足这个条件,我们进行下一步判断 if node.isEnd. 如果满足这个要求了,那我们看到的是一个好兆头,因为这意味着我们至少有一个单词匹配上了,接下来需要做的是把之后的单词进行dfs搜索。 注意下一步的递归函数中,node变成, 起始的root,因为我们要重新从头开始搜索单词了。word不变,i也不变。space变成True. 如果没有满足if node.isEnd 并且我们发现word[i] not in node.children, 那我们可以直接返回False,如果在node.children中,那我们

也进入下一步搜索。node变为node.children[word[i]], i 变为i+1, space不变。 

class TrieNode:
    def __init__(self):
        self.children = {}
        self.isEnd = False 
        
class Trie:
    def __init__(self, words):
        self.root = TrieNode()
        for word in words:
            if word:
                self.insert(word)
            
    def insert(self, word):
        node = self.root 
        for ch in word:
            if ch not in node.children:
                node.children[ch] = TrieNode()
            node = node.children[ch]
        node.isEnd = True 

class Solution:
    def findAllConcatenatedWordsInADict(self, words: List[str]) -> List[str]:
        self.trie = Trie(words)
        res = []
       
        for word in words:
            if self.dfs(self.trie.root, word,0, False):
                res.append(word)
                
        return res 
    
    def dfs(self, node, word, i, space):
        if i == len(word):
            return node.isEnd and space 
        if node.isEnd:
            if self.dfs(self.trie.root, word, i, True):
                return True 
        if word[i] not in node.children:
            return False 
        else:
            return self.dfs(node.children[word[i]], word, i+1, space)

方法三: dp

dp[i] 代表了s[i:len(s)] 的分割情况。dp[i] = 0 意味s[i:len(s)] 不能成功的分割。dp[i] = 1 意味s[i:len(s)] 存在在word list中,但是不能成功的分割。dp[i] = 2 意味s[i:len(s)] 可以被成功得分割。

步骤:1 在主函数中对于每一个word,先构造dp = {len(word):1}. 我们对这个word进行递归搜索. def recursive(self, s, start, n)

         2 在递归函数中,主要需要做的事是判断s[i:j] 是否在word set中,如果在的话,递归调用。self.recursive(s, j, n). 如果递归调用的值不是0的话,那我们可以把dp[i] 更新为2 (如果j不等于len(word),

否则dp[i]=1) 这时候记得把dp[i]就return掉,因为我们已经得到了我们想要的dp值,如果找不到这样的值,dp[i] = 0 

class Solution:
    def findAllConcatenatedWordsInADict(self, words: List[str]) -> List[str]:
        res = []
        self.word_set = set(words)
        for word in words:
            self.dp = {len(word):1}
            if self.check(word,0) > 1:
                res.append(word)
                
        return res 
    
    def check(self, word, start):
        if start in self.dp:
            return self.dp[start]
        j = start + 1 
        while j <= len(word):
            if word[start:j] in self.word_set and self.check(word,j):
                self.dp[start] = 2 if j < len(word) else 1 
                return self.dp[start]
            j += 1
            
        self.dp[start] = 0 
        return self.dp[start]