题目描述:
哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!“已经变成了"iresetthecomputeritstilldidntboot”。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。
注意:本题相对原题稍作改动,只需返回未识别的字符数
思路:一开始没看出是dp,以为是模拟。。然后就签到失败了。。
首先是dp式子:dp[j]表示给出sentence的(0,j)串的最大匹配数
我的思路是求最少未识别符号可以转换为求最多识别的字符数(虽然不转换比较直接),写出状态转移方程:(i >=1 j >=1)
dp[j] = max(dp[j] , dp[j - 1] (把之前子串的最大匹配数传过来)
dp[j] = max(dp[i] + j - i + 1 , dp[j ] ) (sentence的子串(i,j)匹配到了字典中的单词)(可以选择该单词或者不选)
然后是trie树,对sentence每一个位置都是用tire树进行匹配即可,我这里使用正向匹配,官方题解是逆向匹配,但是其实都一样。虽然正向写代码似乎饶了一点
时间复杂度为O(n*n + m),n为sentence的长度,m为字典的字符量(为建树的时间)
c++代码:
struct node {
int child[26] = {0};
bool yz = 0; //true表示是一个单词末尾
};
class Solution {
public:
const static int maxn = 2e5;
node tire[maxn];
int dp[2000] , cnt = 0;
int insert(string &s) {
int u = 0;
for(int j = 0 ; j < s.length() ; j++) {
int t = s[j] - 'a';
if(!tire[u].child[t]) {
tire[u].child[t] = cnt + 1;
cnt++;
}
u = tire[u].child[t];
}
tire[u].yz = 1;
return u;
}
int respace(vector<string>& dictionary, string sentence) {
int u = 0 , len = sentence.length();
for(int i = 0 ; i < dictionary.size() ; i++) {
u = insert(dictionary[i]);
}
for(int i = 0 ; i < len ; i++) {
u = 0;
for(int j = i ; u && j < len; j++) {
int t = sentence[j] - 'a';
u = tire[u].child[t];
if(!u)break;
if(tire[u].yz) {
dp[j + 1] = max(dp[i] + j - i + 1 , dp[j + 1]);
}
}
dp[i + 1] = max(dp[i + 1] , dp[i]);
}
return len - dp[len - 1];
}
};