题目链接:恢复空格
题目描述:
哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!“已经变成了"iresetthecomputeritstilldidntboot”。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。
注意:本题相对原题稍作改动,只需返回未识别的字符数
示例:
输入:
dictionary = [“looked”,“just”,“like”,“her”,“brother”]
sentence = “jesslookedjustliketimherbrother”
输出: 7
解释: 断句后为"jess looked just like tim her brother",共7个未识别字符。
题目分析:
定义dp[i] 表示考虑前i个字符最少的未识别的字符数量,从前往后计
算dp值。
考虑转移方程,每次转移的时候我们考虑第j(j≤i)个到第i个字符
组成的子串sentence[j 一1…i- 1] (注意字符串下标从0开始)
是否能在词典中找到,如果能找到的话按照定义转移方程即为
dp[i] = min( dp[i], dp[j - 1])
否则没有找到的话我们可以复用dp[i - 1]的状态再加,上当前未被识别
的第i个字符,因此此时dp值为
dp[i]= dp[i - 1]+ 1
那么此时问题的关键就是如何高效的查找一个字符串是否在一个字符串(字典)里面,很容易想到用字典树去做,字典树就是用来查找某个字符串(前缀)是否出现过。由于我们要找的是i前面的字符串,即[j…i]的字符串,正常的思路是正着去查找判断,即j从[1,i],也是可以的,不过为了进一步降低复杂度,我们可以倒着查找判断,j从[i,1],去查找字符i的后缀字符串,如果某个[j,i ]不是以字符i为结尾的字符串,那么j前面的也一定不是了,直接退出判断即可。因此,为了方便查找,我们将字典里面的字符串倒序插入,这样从根节点到叶子结点的就是以字符i结尾的字符串。
代码:
class Solution {
public int respace(String[] dictionary, String sentence) {
TireNode root = new TireNode();
//建树
for (String str : dictionary) {
root.insert(root, str);
}
int n = sentence.length();
int[] dp = new int[n+1];
Arrays.fill(dp, 1000000);
dp[0] = 0;
for(int i = 1;i <= n;i++){
dp[i]=dp[i-1]+1;//默认自己为未识别字符
TireNode cur = root;
for(int j = i;j >= 1;j--){
int te = sentence.charAt(j-1)-'a';
if(cur.sons[te]==null){//某个[j,i]不是以字符i为结尾的字符串,直接退出
break;
}
//找到了就更新dp值,isEnd为true表示这是一个完整的单词,是在字典里面的
else if(cur.sons[te].isEnd){
dp[i] = Math.min(dp[i],dp[j-1]);
}
if(dp[i]==0) break;//前面的完全匹配,直接退出
cur = cur.sons[te];
}
}
return dp[n];
}
}
class TireNode{
TireNode[] sons;
boolean isEnd ;
TireNode(){
sons = new TireNode[26];
isEnd = false;
}
public void insert(TireNode root,String str){
TireNode node = root;
int index = 0;
for(int i = str.length()-1;i >= 0;i--){
index = str.charAt(i)-'a';
if(node.sons[index]==null){
node.sons[index] = new TireNode();
}
node = node.sons[index];
}
node.isEnd = true;
}
}
其实这道题不就是字符串的匹配问题吗,找一个字符串是否是另一个字符串的字串,字符串匹配算法,比较高效的有kmp,不过今天我学到了一个新的Rabin-Karp 算法,那就用这个来写吧
关于这个算法,这里贴一个通俗易懂的解答链接轻松理解RK算法
class Solution {
static final long P = Integer.MAX_VALUE;
static final long BASE = 41;
public int respace(String[] dictionary, String sentence) {
int n = sentence.length();
Set<Long> hashValues = new HashSet<Long>();
//计算字典表的hash表
for (String word : dictionary) {
hashValues.add(getHash(word));
}
//dp思路一样,只不过将字符串查找算法进行替换
int[] dp = new int[n + 1];
Arrays.fill(dp, 1000000);
dp[0] = 0;
for (int i = 1; i <= sentence.length(); ++i) {
dp[i] = dp[i - 1] + 1;
long hashValue = 0;
for (int j = i; j >= 1; --j) {
//计算[j....i]子串的hash值
int t = sentence.charAt(j - 1) - 'a' + 1;
hashValue = (hashValue * BASE + t) % P;
//找到的话就更新值
if (hashValues.contains(hashValue)) {
dp[i] = Math.min(dp[i], dp[j - 1]);
}
}
}
return f[sentence.length()];
}
//自定义hash函数
public long getHash(String s) {
long hashValue = 0;
for (int i = s.length() - 1; i >= 0; --i) {
hashValue = (hashValue * BASE + s.charAt(i) - 'a' + 1) % P;
}
return hashValue;
}
}