算法设计与分析第十一周——动态规划之Concatenated Words
这周还是继续做动态规划相关的题目—— 472. Concatenated Words。
题目详情
题目为给出一个字符串集合,找出里面所有的可以由其他字符串(最少两个)组成的字符串。字符串集合里的字符串全是两两不相同的。
输入样例及其解释如下:
| 输入 | 输出 | 解释 |
["cat","cats","catsdogcats", "dog","dogcatsdog", "hippopotamuses","rat", "ratcatdogcat"] | ["catsdogcats", "dogcatsdog", "ratcatdogcat"] | 字符串"catsdogcats"能够由字符串集合里的"cats"和"dog"组成,其他字符串类似。 |
题目分析与算法设计
咋一看,跟之前博主写的Word break(详情可转至Word Break)十分相像,只要把字符串集合里的每一个字符串作为Word break里的s字符串输入,剩余的集合里的所有字符串组成了Word break的字符串词典,就能判断该传入的字符串s能否分裂成字典里的字符串了,即在s可以由字典的字符串组成。
于是,便开始愉快的编写我的程序了,维护一个保存结果的字符串数组 ans,初始化为空集合。可以把之前的wordBreak(string s, vector<string>& wordDict) 函数(返回类型为bool)作为接口,对该题目给定的字符串数组的每一项都进行判断,如果该项可以被拆分,那么加入到 ans 中,遍历给定字符串数组,最后返回 ans。
代码详情
class Solution {
private:
set<string> mywords;
public:
bool isConcatenatedWord(int& index, vector<string>& words) {
string currstr = words[index];
for (int i = 0; i < words.size(); i ++) {
if (i != index) {
mywords.insert(words[i]);
}
}
bool dp[currstr.size() + 1];
memset(dp, false, currstr.size() + 1);
dp[0] = true;
for (int j = 1; j <= currstr.size(); j ++) {
for (int i = j - 1; i >= 0; i --) {
if (dp[i] && mywords.find(currstr.substr(i, j - i)) != mywords.end()) {
dp[j] = true;
}
}
}
mywords.clear();
return dp[currstr.size()];
}
vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
if (words.empty()) return vector<string>();
vector<string> ans;
for (int i = 0; i < words.size(); i ++) {
if (words[i] != "" && isConcatenatedWord(i, words)) {
ans.push_back(words[i]);
}
}
return ans;
}
};
测试了一下,发现题目给出的样例是完全满足的,但是提交之后发现出现TLE(Time Limit Exceeded),也就是超时了,确实,上面的算法的复杂度是比较高的,最差为 O(m * n ^ 2),其中 m 为字符串集合的长度,n 为最长字符串的长度。
稍作优化修改之后得到以下的代码:
class Solution {
private:
set<string> mywords;
public:
bool isConcatenatedWord(int& index, vector<string>& words) {
string currstr = words[index];
int n = currstr.size();
vector<bool> dp(n + 1, false);
dp[0] = true;
for (int i = 0; i < n; i ++) {
if (!dp[i]) continue;
for (int j = i + 1; j <= n; j ++) {
if (j - i < n && mywords.find(currstr.substr(i, j - i)) != mywords.end()) {
dp[j] = true;
}
}
if (dp[n]) return true;
}
return dp[n];
}
vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
if (words.empty()) return vector<string>();
mywords = set<string>(words.begin(), words.end());
vector<string> ans;
for (int i = 0; i < words.size(); i ++) {
if (words[i] != "" && isConcatenatedWord(i, words)) {
ans.push_back(words[i]);
}
}
return ans;
}
};
主要的修改有两处:
- 第13行的 if (!dp[i]) continue; 此处省去了之前的当前的字符串在 i 处不能被拆分仍遍历查询的情况,减少了无用的遍历
- 第19行的 if (dp[n]) return true; 当查到当前字符串可以被拆分,就直接返回真,而不用继续去遍历剩下的情况。
总结与思考
如何把一些无用的遍历考虑到,进而对自己的代码在不更改基本框架和结构的前提下尽可能的优化,也是十分重要的。
谢谢阅读。
4122




被折叠的 条评论
为什么被折叠?



