题目
思路
动态规划+字典树
本题的主要思路其实是动态规划:
- 数组dp[i]的状态表示:前i个字符中,未识别的字符个数。
- 状态转移方程:
- 若存在j(j<=i),使得字符串sentence[j-1…i-1](下标从0开始)存在于dictionary中,则dp[i]=min(dp[i],dp[j-1]);
- 否则,dp[i]=dp[i-1]+1;
此时,问题转化为,在dictionary中匹配字符串的问题,若一个一个暴力匹配,时间消耗太大,本题采用字典树来匹配。
字典树的优点:若某个前缀不是所有字符串的前缀,则可提前终止匹配。
字典树构建:本题因为要从i开始往前遍历(往0的方向),所以,按dictionary中字符串的倒序构建。
具体构建图示见官方题解:
(图源官方题解,侵删)
c++代码
class Trie {
public:
Trie* next[26] = { NULL }; //26个字母
bool is_end; //字符串结束标志
Trie() { //构造函数
is_end = false;
}
void insert(string s) {
Trie* curpos = this;//初始根节点
for (int i = s.size() - 1; i >= 0; --i) { //倒序构建
int id = s[i] - 'a';
if (curpos->next[id] == NULL)
curpos->next[id] = new Trie();
curpos = curpos->next[id];
}
curpos->is_end = true;//每个字符串的 尾节点设置字符串结束标志
}
};
class Solution {
public:
int respace(vector<string>& dictionary, string sentence) {
int n = sentence.size();
if (n == 0)return 0;
Trie *root = new Trie();
for (string dic : dictionary)
root->insert(dic);
vector<int>dp(n+1);
for (int i = 1; i <= n; ++i) {
dp[i] = dp[i - 1] + 1;
Trie *cur = root;
for (int j = i; j >= 1; --j) {
int t = sentence[j-1] - 'a';
if (cur->next[t] == NULL) //该后缀不存在
break;
else {
if (cur->next[t]->is_end)
dp[i] = min(dp[i], dp[j-1]);
if (dp[i] == 0)break;//没有字符未识别,可直接跳出循环
}
cur = cur->next[t];
}
}
return dp[n];
}
};