一、题目描述:
给出一个字典,求指定字符串是否能用字典中单词组合而来。Leetcode上分139,140两题。
139题只要返回是否能用字典中单词组成该字符串,140题还需要返回所有的可行的分割结果。
二、通用解法
两题是有联系的,可以使用搜索来解决,但是需要使用map结构来暂存中间结果,避免重复搜索相同的子问题。
大多数搜索,都是找到一个策略将"大问题"化为"小问题"。
先说140题,然后将代码稍作修改,就是139题目的方案。
三、代码方案
140题:
运用helper递归函数来做。
能看出其中的猫腻。
result是当前s的结果。
s分为rem和word。
m这个unordered_map结构是把搜索结果存储的,省去递归对重复子问题的求解。
去掉unordered_map的相关代码,程序也能完成,但是超时。
class Solution {
public:
vector<string> wordBreak(string s, const vector<string> &dict_) {
dict.insert(dict_.begin(), dict_.end());
return helper(s);
}//wordBreak
private:
vector<string> helper(string s) {
if (m.find(s) != m.end()) return m[s]; //如果有中间结果,就直接取
vector<string> result;
if (dict.find(s) != dict.end())result.push_back(s);//对应rem=“”的情况,也是下面for循环中i=0的情况
int len = s.length();
for (int i(1); i < len; ++i) {
string word = s.substr(i);
if (dict.find(word) == dict.end())continue;
string rem = s.substr(0, i);
vector<string> prev = helper(rem);
int sizeOfPrev = (int)prev.size();
for (int j(0); j < sizeOfPrev; ++j) {
prev[j] += " " + word;
}//for j
result.insert(result.end(), prev.begin(), prev.end());
}//for i
m[s] = result; //存储中间结果
return result;
}//helper
unordered_map<string, vector<string>> m;
unordered_set<string> dict;
};
139题
m改为unordered_map<string,bool>,
helper返回bool,
result定义为bool类型,
if(prev==true)result=true;
给出代码如下,同样的,如果去掉m这个暂存map的优化,也会超时。
class Solution {
public:
bool wordBreak(string s, const vector<string> &dict_) {
dict.insert(dict_.begin(), dict_.end());
return helper(s);
}//wordBreak
private:
bool helper(string s) {
if (m.find(s) != m.end()) return m[s];
bool result(false);
if (dict.find(s) != dict.end())result = true;
int len = s.length();
for (int i(1); i < len; ++i) {
string word = s.substr(i);
if (dict.find(word) == dict.end())continue;
string rem = s.substr(0, i);
bool prev = helper(rem);
if (prev == true)result = true;
}//for i
m[s] = result;
return result;
}//helper
unordered_map<string, bool> m;
unordered_set<string> dict;
};
四、代码方案。
讨论unordered_map这个暂存结构。在搜索中,通常是使用递归来编程,会遇到相同子问题的情况,这种子问题的求解会重复占用时间。
unordered_map这个暂存结构就是暂存之前的努力成果,将它们保存在内存中。用“土地换和平”。
类似于dp算法的思想。
下面给出用dp来解139题的方案。
这种题目还是dp算法管用。
时间复杂度O(len*len),空间O(len),但还是挺有效的,比起搜索。
dp[i]表示[0,i)是否可以用字典组成。
class Solution {
public:
bool wordBreak(string s, const vector<string> &wordDict) {
set<string> wordDictSet(wordDict.begin(),wordDict.end());
int len = s.length();
vector<bool> dp(len+1, false);
dp[0] = true;
for (int i(1); i <= len; ++i) {
for (int j(0); j < i; ++j) {
if (dp[j] && wordDictSet.find(s.substr(j, i - j)) != wordDictSet.end()) {
dp[i] = true;
break;
}//if
}//for j
}//for i
//for (auto tmp : dp)if (tmp)printf("1 "); else printf("0 "); putchar(10);
return dp[len];
}//wordBreak
};