周日参加了leetcode的contest,莫名其妙的被这道题卡住了,给出的反馈是内存超出,就觉得很郁闷,经过今天的好好研究终于发现了原因(根据错误样例反推原因)。
Given a list of words, 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”.
其实这道题,整体思路还是比较清晰的。要判断一个集合中的字符串能否被本集合中的其他字符串拼接而成,那么首先可以先对这个集合中的字符串按照字符长短的顺序进行排序(长的字符串只可能由比其更短的字符串拼接成)。故当我们排完序之后,在判断第i个词的时候,我们的dict里是由前面的i-1个词组成的。剩下的思路就 和word break几乎一样了。无非就是一些dp的思想去保存状态,这里我还是总结一下,给出两种常用的思想,供大家参考学习(直接用dfs搜索会导致超时)。
1 先直接上代码
bool com(const string a, const string b)
{
return a.size() < b.size();
}
class Solution {
public:
bool judge(string s, unordered_set<string>& wordDict,vector<bool>& dp,int before)
{
if(s.size() == 0)
return true;
else
{
for(int i=s.size()-1;i>=0;i--) //那个错误的样例就是这样逆序该对的
{
string a=s.substr(0,i+1);
if(wordDict.find(a) != wordDict.end())
{
string b=s.substr(i+1);
if(dp[i+1+before])
{
if(judge(b,wordDict,dp,before+i+1))
return true;
else
dp[i+1+before]=false;
}
}
}
return false;
}
}
vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
vector<string> ans;
unordered_set<string> dict;
sort(words.begin(),words.end(),com);
dict.insert(words[0]); // 排完序之后,0是最短的字符串,不可能被其他的字符串拼接而成
for(int i=1;i<words.size();i++)
{
vector<bool> dp(words.size()+1,true);
if(judge(words[i],dict,dp,0))
ans.push_back(words[i]);
dict.insert(words[i]);
}
return ans;
}
};
这种思路dp[i]保存的是判断从下标i开始一直到结尾的字符串是否能够被dict中的词语拼成。注意judge函数中的before变量,因为在每一次递归调用的时候当前字符串的部分前缀会被砍去,那么传到下一层的就是前缀被砍的字符串,那么对应的index是在原来总的字符串的位置,那么我们就需要记录之前被砍了多少的字符,以便获得正确的对应在原来字符串中的index。由于用到了递归函数的写法,故在一些特别变态的样例下会超时(因为递归调用的次数太多了,申请了过多的堆栈空间,我就是这样被莫名其妙卡住的)。根据错误样例把judge中的循环倒过来写就过了,从本质上来讲并没有简化多少,只不过针对这一批的测试样例罢了。
2 其实前面部分都是一样的,只是judge函数不一样。这里为了避免赘述,直接给出了judge函数。
bool judge(unordered_set<string>& wordDict, string s){
vector<bool> dp(s.size()+1,false);
dp[0] = true;
for(int i = 1; i <= s.size(); i++)
{
for(int j = i - 1; j >= 0; j--)
{
if(dp[j])
{
string tmp = s.substr(j,i-j);
if(wordDict.find(tmp) != wordDict.end())
{
dp[i] = true;
break;
}
}
}
}
return dp[s.size()];
}
};
这里和上面的方法不同,dp[i]表示的是从字符串的头开始长度为i的字符串能否被dict中的词语拼成,由于i表示的是字符串的长度,故在初始化的时候dp的大小是s.size()+1。这种dp方法比较好理解,只需要注意字符串的小标就好了。