哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"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个未识别字符。
提示:
- 0 <= len(sentence) <= 1000
- dictionary中总字符数不超过 150000。
- 你可以认为dictionary和sentence中只包含小写字母。
这一题直接采用dp和字典树来实现会比较简单
由后向前遍历字符串:dp[i]是以i开始的后半字符串中,无法匹配到词表的最小字符数
dp[i] = min(dp[i-j] (匹到单词长j), dp[i-j2] (匹到单词长j2),... , dp[i+1]+1(不匹配任何单词,依赖上一次匹配) )
func respace(dictionary []string, sentence string) int {
if len(dictionary) == 0 || len(sentence) == 0 {
return 0
}
// 构造字典树O(len)
trie := NewTrie()
for i := range dictionary {
trie.Add(dictionary[i])
}
dp := make([]int, len(sentence))
// 从后向前遍历,最差时间复杂度O(n*n)dp[i] = min(dp[i-j](匹到单词长j), dp[i-j2](匹到单词长j2),... )
// "abc" i=2, j=3, s=c 查的到,则dp[2]=dp[3]
for i := len(sentence)-1; i >= 0; i-- {
// 这里置个初值,也就是默认当前字符匹不上任何单词
if i < len(sentence)-1 {
dp[i] = dp[i+1]+1
} else {
dp[i] = 1
}
// 这里查看i...len的每个可组成单词,并判断最小值
for j := i+1; j <= len(sentence); j++ {
if trie.Search(sentence[i:j]) {
if j < len(sentence) {
// 这里针对abcdef, 假如ab是个词, bcde是个词,那 dp[0] = min(dp[1]+1=2,dp[2]=4),还是不匹ab比较小
dp[i] = min(dp[i], dp[j])
} else {
// 直接干到末尾,则置为0,以覆盖前面置的初值 dp[i] = dp[i+1]+1
dp[i] = 0
}
}
}
}
return dp[0]
}
func min(i,j int) int {
if i < j {
return i
}
return j
}
type Trie struct {
child []*Trie
value byte
isEnd bool
}
func NewTrie() *Trie {
return &Trie{
child: make([]*Trie, 26),
}
}
func (t *Trie) Search(s string) bool {
node := t
for i := range s {
c := s[i] - 'a'
if node.child[c] == nil {
return false
}
node = node.child[c]
}
return node.isEnd
}
func (t *Trie) Add(s string) {
node := t
for i := range s {
c := s[i] - 'a'
if node.child[c] == nil {
node.child[c] = &Trie{
child: make([]*Trie, 26),
value: s[i],
isEnd: false,
}
}
node = node.child[c]
}
node.isEnd = true
}