LeetCode力扣官方题解472。连接词
题目描述
给你一个 不含重复 单词的字符串数组 words ,请你找出并返回 words 中的所有 连接词 。
连接词 定义为:一个完全由给定数组中的至少两个较短单词组成的字符串。
示例 1: 输入: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" 组成。
示例 2: 输入:words = ["cat","dog","catdog"] 输出:["catdog"]
提示: 1 <= words.length <= 10 ⁴ 0 <= words[i].length <= 1000 words[i] 仅由小写字母组成 0 <= sum(words[i].length) <= 10 ⁵
解决方案
前言
本文的解法需要使用字典树。如果读者对字典树不了解,建议首先阅读「208. 实现 Trie (前缀树) 的官方题解」,在理解字典树的实现之后继续阅读本文。
方法一:字典树 + 记忆化搜索
思路与算法
判断一个单词是不是连接词,需要判断这个单词是否完全由至少两个给定数组中的更短的非空单词(可以重复)组成。判断更短的单词是否在给定数组中,可以使用字典树实现。
为了方便处理,首先将数组 words 按照字符串的长度递增的顺序排序,排序后可以确保当遍历到任意单词时,比该单词短的单词一定都已经遍历过,因此可以根据已经遍历过的全部单词判断当前单词是不是连接词。
在将数组 words 排序之后,遍历数组,跳过空字符串,对于每个非空单词,判断该单词是不是连接词,如果是连接词则将该单词加入结果数组,如果不是连接词则将该单词加入字典树。
判断一个单词是不是连接词的做法是在字典树中搜索。从该单词的第一个字符(即下标 0 处的字符)开始,在字典树中依次搜索每个字符对应的结点,可能有以下几种情况: 如果一个字符对应的结点是单词的结尾,则找到了一个更短的单词,从该字符的后一个字符开始搜索下一个更短的单词; 如果一个字符对应的结点在字典树中不存在,则当前的搜索结果失败,回到上一个单词的结尾继续搜索。
如果找到一个更短的单词且这个更短的单词的最后一个字符是当前单词的最后一个字符,则当前单词是连接词。由于数组 words 中没有重复的单词,因此在判断一个单词是不是连接词时,该单词一定没有加入字典树,由此可以确保判断连接词的条件成立。
由于一个连接词由多个更短的非空单词组成,如果存在一个较长的连接词的组成部分之一是一个较短的连接词,则一定可以将这个较短的连接词换成多个更短的非空单词,因此不需要将连接词加入字典树。
为了降低时间复杂度,需要使用记忆化搜索。对于每个单词,创建与单词相同长度的数组记录该单词的每一个下标是否被访问过,然后进行记忆化搜索。搜索过程中,如果一个下标已经被访问过,则从该下标到末尾的部分一定不是由给定数组中的一个或多个非空单词组成(否则上次访问时已经可以知道当前单词是连接词),只有尚未访问过的下标才需要进行搜索。
代码
Python3 class Trie: def __init__(self): self.children = [None] * 26 self.isEnd = False def insert(self, word: str): node = self for ch in word: ch = ord(ch) - ord("a") if not node.children[ch]: node.children[ch] = Trie() node = node.children[ch] node.isEnd = True def dfs(self, word: str, start: int, vis: List[bool]) -> bool: if start == len(word): return True if vis[start]: return False vis[start] = True node = self for i in range(start, len(word)): node = node.children[ord(word[i]) - ord("a")] if node is None: return False if node.isEnd and self.dfs(word, i + 1, vis): return True return False class Solution: def findAllConcatenatedWordsInADict(self, words: List[str]) -> List[str]: words.sort(key=len) ans = [] root = Trie() for word in words: if word == "": continue if root.dfs(word, 0, [False] * len(word)): ans.append(word) else: root.insert(word) return ans
C++ struct Trie { bool isEnd; vector children; Trie() { this->children = vector(26, nullptr); this->isEnd = false; } }; class Solution { public: Trie * trie = new Trie(); vector findAllConcatenatedWordsInADict(vector& words) { vector ans; sort(words.begin(), words.end(), [&](const string & a, const string & b){ return a.size() < b.size(); }); for (int i = 0; i < words.size(); i++) { string word = words[i]; if (word.size() == 0) { continue; } vector visited(word.size(), 0); if (dfs(word, 0, visited)) { ans.emplace_back(word); } else { insert(word); } } return ans; } bool dfs(const string & word, int start, vector & visited) { if (word.size() == start) { return true; } if (visited[start]) { return false; } visited[start] = true; Trie * node = trie; for (int i = start; i < word.size(); i++) { char ch = word[i]; int index = ch - "a"; node = node->children[index]; if (node == nullptr) { return false; } if (node->isEnd) { if (dfs(word, i + 1, visited)) { return true; } } } return false; } void insert(const string & word) { Trie * node = trie; for (int i = 0; i < word.size(); i++) { char ch = word[i]; int index = ch - "a"; if (node->children[index] == nullptr) { node->children[index] = new Trie(); } node = node->children[index]; } node->isEnd = true; } };
Java class Solution { Trie trie = new Trie(); public List findAllConcatenatedWordsInADict(String[] words) { List ans = new ArrayList(); Arrays.sort(words, (a, b) -> a.length() - b.length()); for (int i = 0; i < words.length; i++) { String word = words[i]; if (word.length() == 0) { continue; } boolean[] visited = new boolean[word.length()]; if (dfs(word, 0, visited)) { ans.add(word); } else { insert(word); } } return ans; } public boolean dfs(String word, int start, boolean[] visited) { if (word.length() == start) { return true; } if (visited[start]) { return false; } visited[start] = true; Trie node = trie; for (int i = start; i < word.length(); i++) { char ch = word.charAt(i); int index = ch - "a"; node = node.children[index]; if (node == null) { return false; } if (node.isEnd) { if (dfs(word, i + 1, visited)) { return true; } } } return false; } public void insert(String word) { Trie node = trie; for (int i = 0; i < word.length(); i++) { char ch = word.charAt(i); int index = ch - "a"; if (node.children[index] == null) { node.children[index] = new Trie(); } node = node.children[index]; } node.isEnd = true; } } class Trie { Trie[] children; boolean isEnd; public Trie() { children = new Trie[26]; isEnd = false; } }
Golang type trie struct { children [26]*trie isEnd bool } func (root *trie) insert(word string) { node := root for _, ch := range word { ch -= "a" if node.children[ch] == nil { node.children[ch] = &trie{} } node = node.children[ch] } node.isEnd = true } func (root *trie) dfs(vis []bool, word string) bool { if word == "" { return true } if vis[len(word)-1] { return false } vis[len(word)-1] = true node := root for i, ch := range word { node = node.children[ch-"a"] if node == nil { return false } if node.isEnd && root.dfs(vis, word[i+1:]) { return true } } return false } func findAllConcatenatedWordsInADict(words []string) (ans []string) { sort.Slice(words, func(i, j int) bool { return len(words[i]) < len(words[j]) }) root := &trie{} for _, word := range words { if word == "" { continue } vis := make([]bool, len(word)) if root.dfs(vis, word) { ans = append(ans, word) } else { root.insert(word) } } return }
C# public class Solution { Trie trie = new Trie(); public IList FindAllConcatenatedWordsInADict(string[] words) { IList ans = new List(); Array.Sort(words, (a, b) => a.Length - b.Length); for (int i = 0; i < words.Length; i++) { string word = words[i]; if (word.Length == 0) { continue; } bool[] visited = new bool[word.Length]; if (DFS(word, 0, visited)) { ans.Add(word); } else { Insert(word); } } return ans; } public bool DFS(string word, int start, bool[] visited) { if (word.Length == start) { return true; } if (visited[start]) { return false; } visited[start] = true; Trie node = trie; for (int i = start; i < word.Length; i++) { char ch = word[i]; int index = ch - "a"; node = node.children[index]; if (node == null) { return false; } if (node.isEnd) { if (DFS(word, i + 1, visited)) { return true; } } } return false; } public void Insert(string word) { Trie node = trie; for (int i = 0; i < word.Length; i++) { char ch = word[i]; int index = ch - "a"; if (node.children[index] == null) { node.children[index] = new Trie(); } node = node.children[index]; } node.isEnd = true; } } class Trie { public Trie[] children; public bool isEnd; public Trie() { children = new Trie[26]; isEnd = false; } }
C typedef struct Trie { struct Trie * children[26]; bool isEnd; }Trie; #define TRIE_INITIAL(node) do { for (int i = 0; i < 26; ++i) { (node)->children[i] = NULL; } (node)->isEnd = false; }while(0); static void freeTrie(Trie * node) { if (NULL == node) { return; } for (int i = 0; i < 26; ++i) { if (node->children[i] != NULL) { freeTrie(node->children[i]); } } free(node); } static int cmp(const void * pa, const void * pb){ int la = strlen(*(char **)pa); int lb = strlen(*(char **)pb); return la - lb; } bool dfs(Trie * trie, const char * word, int wordSize, int start, int* visited) { if (wordSize == start) { return true; } if (visited[start]) { return false; } visited[start] = 1; Trie * node = trie; for (int i = start; i < wordSize; i++) { char ch = word[i]; int index = ch - "a"; node = node->children[index]; if (node == NULL) { return false; } if (node->isEnd) { if (dfs(trie, word, wordSize, i + 1, visited)) { return true; } } } return false; } void insert(Trie * trie, const char * word, int wordSize) { Trie * node = trie; for (int i = 0; i < wordSize; i++) { char ch = word[i]; int index = ch - "a"; if (node->children[index] == NULL) { node->children[index] = (Trie *)malloc(sizeof(Trie)); TRIE_INITIAL(node->children[index]); } node = node->children[index]; } node->isEnd = true; } char ** findAllConcatenatedWordsInADict(char ** words, int wordsSize, int* returnSize){ int pos = 0; char ** ans = (char **)malloc(sizeof(char *) * wordsSize); Trie * trie = (Trie *)malloc(sizeof(Trie)); TRIE_INITIAL(trie); qsort(words, wordsSize, sizeof(char *), cmp); for (int i = 0; i < wordsSize; i++) { int len = strlen(words[i]); if (len == 0) { continue; } int * visited = (int *)malloc(sizeof(int) * len); memset(visited, 0, sizeof(int) * len); if (dfs(trie, words[i], len, 0, visited)) { ans[pos] = (char *)malloc(sizeof(char) * (len + 1)); strncpy(ans[pos], words[i], len); ans[pos][len] = " "; pos++; } else { insert(trie, words[i], len); } free(visited); } freeTrie(trie); *returnSize = pos; return ans; }
复杂度分析 时间复杂度:
其中 n 是数组 words 的长度,li 是单词 words[i] 的长度。对数组 words 按照字符串的长度递增的顺序排序需要 O(nlog n) 的时间,判断单词 words[i] 是不是连接词的时间复杂度是 O(li)。 空间复杂度:
其中 n 是数组 words 的长度,li 是单词 words[i] 的长度,S 是字符集,这道题中 S 为全部小写英语字母, S =26。空间复杂度主要取决于字典树,最坏情况下需要将所有的单词加入字典树。
BY /
本文作者:力扣
声明:本文归"力扣"版权所有,如需转载请联系。
为何说科比3亿薪资是透支身体换来的,看了这5张照片你就懂科比的离开令人心痛,但是他影响力依旧巨大,特别是曼巴精神,更是激励了无数人。科比打球好看,数据又爆炸,他自然是联盟宠儿。既然是联盟的宠儿,商业价值肯定高,仅是工资20年就赚了超3。
怀上三胎,身体状况良好铃木亚美宣布怀上三胎据日媒消息报道,歌手铃木亚美于4月5日通过Instagram社交平台宣布怀上三胎。铃木和粉丝们分享了这一喜讯本人成功怀上三胎,目前已有6个月的身孕。之前她还特地
6岁女孩勇敢对霸凌说不,不好惹的气质,你家孩子有吗?胆子大外向的孩子和内向不爱说话的孩子,哪个不受人欺负?答案或许跟你想象的不一样,不好惹的气质,你家孩子有吗?我家儿子明明今年6岁了,每天在家里调皮捣蛋和一个小霸王一样,邻居家的女儿
饭后应该如何养生?一做二不做,身体或许越来越好吃饭之后到底怎么做才最养生?这个问题一直困扰着很多人,有的人习惯饭后就躺着休息,也有的人为了防止积食,立刻开始运动究竟哪种方式是正确的呢?实际上,饭后要想让消化正常,身体健康,要记
花开有时人无再少年春日百花盛开,各种裙装急不可待想上身,40的女人有长裙的优雅,半裙的随意,裤装的干练。背带裙却少有选择。总会觉得年纪大各种不适合,我也是如此,高中孩子的妈妈总觉得应该端庄持重然后青
汪小菲妈妈生日,大S录视频送祝福,对前婆婆的称呼成焦点汪小菲跟大S去年11月份官宣离婚,如今两人正式分开已经近半年了,但是有关两人的前尘往事还是不绝于耳,这两日,大S一年前在粉丝群说的话被曝光,大S话语中透露儿子汪晞箖似乎对爸爸汪小菲
大S教子有方,虽已新婚,仍然不忘带儿子给前婆婆庆祝生日理娱plus计划7日,是大S徐熙媛的前婆婆张兰的生日,但是令很多吃瓜群众都没有想到的是,新婚不久的大S竟然主动让她和汪小菲的儿子给奶奶打视频,祝福奶奶生日快乐。孙子自然乖巧可爱,视
春天,孩子长个黄金期,晚餐别将就,多吃高钙食物,营养好吸收春天,孩子长个黄金期,晚餐别将就,多吃高钙食物,营养好吸收春季是孩子生长发育最好的时期,这个时候各位家长们,可别粗心大意了,平时一定要记得,多给孩子做点有营养的东西。春季孩子长个黄
最常见的养胃食物,你知道几种?经济发展的快节奏,迫使现在的人跟上步伐,快餐应运而生。追求速度的同时,人的胃已经告急。十人十个胃口,却有八个胃不好,就是因为平时不注意养胃,造成其负担过重,继而引发胃炎胃溃疡胃癌等
这几种食物有利于乙肝恢复,乙肝患者要了解如果乙肝患者能够给自己制定一份合理的膳食计划,在很大程度上都能够加快乙肝的恢复进程,今天小编就给大家推荐一些乙肝患者适合吃的食物以及平时患者需要注意的不该吃的食物,希望这些食物能更
聚会时尿酸猛增,这几样食物吃了尿酸悄悄降下来节假日期间各种聚会不可避免,不管是为了工作还是为了沟通感情,聚会是最好的桥梁。尤其是亲朋好友相聚在一起,聚会一场接着一场,虽然有欢乐,但是背后也隐藏着健康危机。有不少人因为过多的聚