Re-Space:给定一个字典和一个句子,根据字典将句子进行拆分,使得最终不在字典中的字母数量最少。
最近刷的比较慢,一是因为白天实习没什么功夫,二是因为书上的解法也可能过不了了,三是题确实难了,光凭打印调试效率太低了,看来应该是不能白板写代码了。
最暴力的方法就是依次尝试在每个可能的地方进行拆分,并在所有的可能中选取最优解,时间复杂度为O(2 ^ n)
,n
为句子sentence
的长度,从输入数据规模来看,会达到2 ^ 1000
,所以需要通过剪枝和记忆化的搜索来进行优化。剪枝的方法就是在拆分的过程中就计算已经拆分好的部分的无效字符数量。虽然通过了所有的71
个测试用例,但是依然超时了,但是当我把dict
的类型从set<string>
换成unordered_set<string>
后就过了,我只能说太蠢了。
class Solution {
private:
unordered_set<string> dict;
vector<int> Mem;
int respace(const string &sentence, int idx)
{
if(idx == static_cast<int>(sentence.size())) return 0;
if(Mem[idx] != INT_MAX) return Mem[idx];
string part;
int best = INT_MAX;
for(int i = idx; i < static_cast<int>(sentence.size()); i++)
{
part.push_back(sentence[i]);
int invalid = dict.find(part) != dict.end() ? 0 : part.size();
if(invalid < best){
int RestInvalid = respace(sentence, i + 1);
if(invalid + RestInvalid < best){
best = invalid + RestInvalid;
}
}
}
Mem[idx] = best;
return Mem[idx];
}
public:
int respace(vector<string> &dictionary, string sentence) {
if(sentence.empty()) return 0;
for(const string &word : dictionary)
{
dict.insert(word);
}
Mem.assign(sentence.size(), INT_MAX);
respace(sentence, 0);
return Mem[0];
}
};
上面的解法需要在确定所有拆分的位置后,才能计算sentence
的总无效值,但是由于进行了剪枝优化,在递归的过程中就计算了部分的无效值。更好的方法是在每考察sentence
中的一个字符时,就计算当前整体的最优值,类似背包问题,每当加入一个字符时,尝试将该字符作为所有字串的末尾,计算得到一个最小的无效值。
class Solution {
private:
unordered_set<string> dict;
public:
int respace(vector<string>& dictionary, string sentence) {
if(sentence.empty()) return 0;
for(const string &word : dictionary)
{
dict.insert(word);
}
vector<int> Invalid(1, 0);
for(size_t i = 1; i <= sentence.size(); i++)
{
Invalid.push_back(Invalid.back() + 1);
for(const string &word : dict)
{
size_t len = word.size();
if(len <= i && word == sentence.substr(i - len, len)){
if(Invalid[i - len] < Invalid[i]){
Invalid[i] = Invalid[i - len];
}
}
}
}
return Invalid.back();
}
};