一、题目描述
给定一个连续不包含空格的字符串,该字符串仅包含英文小写字母及英文标点符号(逗号、分号、句号),同时给定词库,对该字符串进行精确分词。
说明:
-
精确分词:字符串分词后,不会出现重叠。即"ilovechina",不同词库可分割为"i,love,china","ilove,china",不能分割出现重叠的"i,ilove,china",i 出现重叠
-
标点符号不成词,仅用于断句
-
词库:根据外部知识库统计出来的常用词汇例:dictionary = ["i", "love", "china", "lovechina", "ilove"]
-
分词原则:采用分词顺序优先且最长匹配原则
"ilovechina",假设分词结果 [i,ilove,lo,love,ch,china,lovechina],则输出 [ilove,china]
错误输出:[i,lovechina],原因:"ilove" > 优先于 "lovechina" 成词
错误输出:[i,love,china],原因:"ilove" > "i"遵循最长匹配原则
二、输入输出描述
输入描述
- 第一行:待分词语句,连续无空格,0 < length < 256
- 第二行:词库字符串,以逗号分隔,1 < length < 100000
输出描述
- 按分词顺序输出结果,词汇以逗号分隔;
- 若某段字符无匹配词汇(标点除外),则该段不输出(或按实际匹配结果)
三、示例
|----|------------------------------------------------------|
| 输入 | ilovechina i,love,china,ch,na,ve,lo,this,is,the,word |
| 输出 | i,love,china |
| 说明 | |
|----|----------------------------------------------------------------|
| 输入 | iat i,love,china,ch,na,ve,lo,this,is,the,word,beauti,tiful,ful |
| 输出 | i,a,t |
| 说明 | 单个字母, 不在词库中且不成词则输出单个字母 |
|----|------------------------------------------------------------------------------------------|
| 输入 | ilovechina,thewordisbeautiful i,love,china,ch,na,ve,lo,this,is,the,word,beauti,tiful,ful |
| 输出 | i,love,china the,word,is,beauti,ful |
| 说明 | 标点符号为英文标点符号 |
四、解题思路
- 核心思想
以最长匹配 + 顺序处理 + 词汇单次消耗为核心,通过队列维护待分词片段,对每个片段从最长长度开始尝试匹配词库,优先选用最长匹配的词汇(保证分词粒度最大化),匹配失败则拆分单个字符,同时词库词汇匹配后立即移除(避免重复使用),最终按顺序输出分词结果。
- 问题本质分析
- 表层问题:将待分词句子按词库拆分,输出指定格式的分词结果;
- 深层问题:
- 分词的 "最优匹配":自然语言分词中,最长匹配是最基础且有效的策略(避免过度拆分,如 "中国" 拆成 "中"+"国");
- 顺序约束:必须按原文顺序分词,因此从片段起始位置开始截取子串;
- 资源消耗:词库词汇是 "有限资源",单次使用后需移除,保证每个词汇仅参与一次匹配;
- 边界处理:无匹配词汇时的兜底逻辑(拆分单个字符),保证分词过程不中断。
- 核心逻辑
- 数据结构选型:用 HashSet 存储词库(快速查找 / 删除),用 LinkedList 模拟队列(按顺序处理待分词片段);
- 最长匹配:对每个待分词片段,从最大长度到 1 截取子串,找到第一个匹配的词库词汇即选用;
- 状态维护:匹配成功则更新结果和词库,剩余片段重回队列;匹配失败则拆分首字符,剩余片段重回队列;
- 结果拼接:将分词结果按顺序拼接为逗号分隔的字符串。
-
步骤拆解
-
输入预处理
- 将待分词句子、词库按
,.;拆分,转为字符串数组; - 词库数组转 HashSet(
wordSet),支持快速查找和删除; - 待分词句子数组转入队列(
queue),保证处理顺序。
- 将待分词句子、词库按
-
循环处理待分词片段当队列非空时,重复以下步骤:
- 取出队首的待分词片段(
sentence); - 最长匹配尝试:从
r = 片段长度开始递减,截取[0, r)的子串:① 若子串在wordSet中:将子串加入结果,从wordSet移除该词汇;若有剩余片段,放回队首;退出循环;② 若遍历完所有长度(r=0):将片段第一个字符加入结果,剩余片段放回队首。
- 取出队首的待分词片段(
-
结果拼接与返回
- 将结果集合(
ans)用逗号分隔拼接为字符串; - 返回拼接后的字符串。
- 将结果集合(
五、代码实现
java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String[] sentences = sc.nextLine().split("[,.;]");
String[] words = sc.nextLine().split("[,.;]");
System.out.println(getResult(sentences, words));
}
public static String getResult(String[] sentences, String[] words) {
// wordSet 记录词库词汇
HashSet<String> wordSet = new HashSet<>(Arrays.asList(words));
// queue记录待分词语句
LinkedList<String> queue = new LinkedList<>(Arrays.asList(sentences));
// ans记录最终分词结果
LinkedList<String> ans = new LinkedList<>();
while (queue.size() > 0) {
// 待分词的句子
String sentence = queue.removeFirst();
int r = sentence.length();
for (; r > 0; r--) {
// 截取句子 [0,r) 范围子串词汇, 这样的就能实现优先最长匹配,并且由于是必须从0索引开始截取,因此满足了分词顺序优先
String fragment = sentence.substring(0, r);
// 若词库中是否存在该子串词汇
if (wordSet.contains(fragment)) {
// 则将对应子串词汇纳入结果
ans.addLast(fragment);
wordSet.remove(fragment); // 我理解词库中每个词汇只能使用一次,因此这里将词库中使用过的词汇移除
// 若子串词汇只是句子部分,则句子剩余部分还要继续去词库中查找
if (r < sentence.length()) {
queue.addFirst(sentence.substring(r));
}
break;
}
}
// 没有在词库中找到对应子串词汇,则输出单个字母
if (r == 0) {
// 注意,这里只放一个字母到结果中,句子剩余部分继续去词库中查找
ans.add(sentence.charAt(0) + "");
if (sentence.length() > 1) {
queue.addFirst(sentence.substring(1));
}
}
}
StringJoiner sj = new StringJoiner(",");
ans.forEach(sj::add);
return sj.toString();
}
}