1. 问题描述:
给定一个不含重复单词的字符串数组 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 ^ 4
0 <= words[i].length <= 1000
words[i] 仅由小写字母组成
0 <= sum(words[i].length) <= 6 * 10 ^ 5
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/concatenated-words
2. 思路分析:
分析题目可以知道我们需要求解出所有的单词能够拆分成words的其余单词并且拆分的单词数量大于等于2的字符串集合,因为涉及到单词的拆分问题所以我们可以尝试使用动态规划递推的思路解决,我们可以考虑每一个单词,对于每一个单词递推出所有可能的拆分状态。动态规划中主要有两个步骤:① 状态表示 ② 状态计算 。对于字符串的动态规划问题我们一般可以使用一维数组或者列表来表示,分析题目我们可以考虑dp[i]表示前i个字符能够拆分单词的最大值,这样状态表示就解决了。怎么样进行状态的转移和计算呢?对于前i个字符我们拆分成两部分,后半部分的长度为j,当当前的dp[i - j] >= 0并且后面长度为j的字符串在words中是存在的说明当前的拆分状态是是合法的(一开始使用哈希表记录下所有words中单词这样可以快速判断单词是否存在),那么更新一下前i个字符能够拆分单词的最大值,拆分的过程其实是在尝试前i个字符所有可能拆分的状态,并且字符数量是递增的,后面的状态是可以由前面的合法状态进行递推和转移的,所以可以求解出前i个字符能够拆分单词的最大值,最终dp[n]表示的就是前n个字符拆分单词数量的最大值,返回dp[n]是否大于1即可。
3. 代码如下:
from typing import List
import collections
class Solution:
def check(self, w: str, dic: collections.defaultdict):
n = len(w)
dp = [-1] * (n + 1)
dp[0] = 0
# 前i个字符, 状态计算的时候需要注意下标的问题
for i in range(1, n + 1):
# 拆分的后半部分的长度为1~i(长度必须大于1才是符合条件的), 枚举前i个字符可能的拆分状态
for j in range(1, i + 1):
if dp[i - j] >= 0 and dic[w[i - j: i]] > 0:
dp[i] = max(dp[i], dp[i - j] + 1)
return dp[-1] > 1
def findAllConcatenatedWordsInADict(self, words: List[str]) -> List[str]:
dic = collections.defaultdict(int)
# 将所有单词存储到哈希表中这样可以在O(1)的时间查询出当前的单词是否存在
for w in words:
dic[w] = 1
res = list()
for i in range(len(words)):
# 对于每一个单词分别考虑能够拆分的最大单词数量
if self.check(words[i], dic):
res.append(words[i])
return res
if __name__ == '__main__':
print(Solution().findAllConcatenatedWordsInADict(
["cat", "dog", "catdog"]))