题目描述:
给你一个 不含重复 单词的字符串数组 words ,请你找出并返回 words 中的所有 连接词 。
连接词 定义为:一个完全由给定数组中的至少两个较短单词组成的字符串。
样例:
方法一:
class Solution {
public:
//字典树类
class trie
{
public:
bool isend; //判断以当前字母为结尾的是否是一个单词
vector<trie*> nextnode;
trie() //构造函数初始化,vector一定要放在这里才能够初始化
{
isend = false;
nextnode = vector<trie*>(26, nullptr);
}
};
trie* root = new trie();
static bool cmp(string str1, string str2)
{
return str1.size() < str2.size();
}
vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
sort(words.begin(), words.end(), cmp);
vector<string> ans;
for (string word : words)
{
if (word == "") continue;
//如果word是一个连接词,那么可以不用再加入字典树中
if (dfs(word, 0))
{
ans.push_back(word);
}
//将不是连接词的单词加入字典树中
else
{
insert(word);
}
}
return ans;
}
//判断该单词是否是一个连接词
bool dfs(string str, int index)
{
//如果查找到了单词的末尾了,说明是一个连接词
if (index == str.size())
{
return true;
}
trie* cur = root;
for (int i = index; i < str.size(); i++)
{
int x = str[i] - 'a';
//如果没有这个单词,直接返回false
if (cur->nextnode[x] == nullptr)
{
return false;
}
//如果以该字母为结尾的前缀构成了一个单词,就继续判断后面的字符串是不是连接词
if (cur->nextnode[x]->isend)
{
if (dfs(str, i + 1))
{
return true;
}
}
//存在一种情况,例如words = ["ca", "cat", "ag", "catag"]
//那么对于catag而言,其首先发现ca是一个单词,但是后续的tag却不是一个连接词,
//所以需要回到上一次dfs,继续判断当cat是一个单词的时候,发现ag也是一个单词,于是catag是一个连接词
//所以当上面的两个if中没法返回true时,这里需要继续判断以下一个字母为尾部是否能够构成一个新的单词
cur = cur->nextnode[x];
}
return false;
}
//字典树的插入单词函数
void insert(string str)
{
trie* cur = root;
for (char ch : str)
{
int x = ch - 'a';
if (cur->nextnode[x] == nullptr)
{
cur->nextnode[x] = new trie();
}
cur = cur->nextnode[x];
}
cur->isend = true;
}
};
一看到困难题就知道又是看题解的一天了,自从大二的数据结构实验课之后,就再也没写过字典树,不过trie学习一下还是不难的,参考以下文章:
【图解算法】模板+变式——带你彻底搞懂字典树(Trie树)_丧心病狂Loli控的博客-CSDN博客_trie树图解
这道题我想过字典树和哈希表哪一个较好,实际上在处理这种具有前缀字符串的问题时,字典树的优势是要明显大于哈希表的。
这个方法让我惊叹的地方在于,一开始先将words中的各个单词按照长短从小到大排序,这样一来对于后面的长单词就可以直接用深度优先搜索来判断其是否是一个连接词,如果是一个连接词就不用加入字典树中,否则再将其加入字典树中。