背景: 最近培训讲到acm了,当年没有好好呆着所以就,特别菜.学习下吧,刷刷水题.
题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1251
这题我傻傻的用String的startsWith()去做了,整挺好,tle了,大概是因为要遍历输入的字符,然后数据量比较大.这的确比较挫.
去看了下思路,然后去了解了下Trie字典树.
看图可以很明显看出来,从一个root节点展开,然后每个字符分别作为子节点继续展开,直到单词结束,将最后遍历到的字符标记成terminal,此处terminal对应我题解中的isEnd.用于告知程序此处是一个单词结束.
也没什么好奖的啦,咱网友水平都是有的嘛,直接上一下题解,都有注释啦应该挺详细了.
package hdu.p1251;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* @author Relic
* @desc hdu1251 统计难题(字典树)
* @date 2019-12-04 19:50
*/
public class Main {
/*
url: http://acm.hdu.edu.cn/showproblem.php?pid=1251
解法字典树: 看别人的思路有两种解法
1.直接map去解
- 遍历输入的数据中的字符,将字符作为字符串放入 map (<String,Integer>)中,如果有则直接+1
- 每次遍历将字符串更新为从第一个到当前遍历到的所有的字符.
形象点: 两个输入 abc acm 遍历完之后为 { a:2, ab:1, abc:1, ac:1, acm:1}
- 输入结束后,去取数量的时候,直接根据输入的字符去 map 中取出 value
- 当然 这就特别挫,挺 low 的,而且随着文本的增加 这个 map 也会非常的,恶心!
2.使用字典树实现.
这里就讲一下字典树的解法
定义一个节点类,四个属性:
- value: 此节点中字符的值
- count: 包含父节点到此路径所代表的的字符串出现的次数
- sons: 子节点元素
- isEnd: 是否已经是节点尾部
字典树主要两个方法: 插入与查询
插入: 1.将输入的字符串转化成字符数组
2.将当前调用方法的节点作为父节点
3.遍历字符数组,根据当前字符定位所处子节点的位置 (从'a'开始,直接减去'a')
4.如果确定的位置上的字典树节点不存在,则新建节点,将字符的值赋值给节点
5.字典树节点出现的次数: count 累加一次
6.将下一次循环要遍历的节点更新为子节点
7.当字符遍历到最后一个时, 将当前节点的isEnd状态更新为true
查询: 1.查找更简单了
2.直接更新字符数组去依次遍历节点,如果其中一个节点为空直接返回0,输出最后节点的 count 值
难点还在于打印字典树.... 但题目其实并不需要这个
*/
/**
* 每个节点最多为26个字母
*/
private static final int NODE_SIZE = 26;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
TrieNode root = new TrieNode();
String str;
while (!"".equals(str = sc.nextLine())) {
root.insert(str);
}
/// 打印测试下字典树的遍历
// System.out.println(root);
while (sc.hasNextLine()) {
System.out.println(root.search(sc.nextLine()));
}
}
static class TrieNode {
StringBuilder word = new StringBuilder();
char value;
int count;
TrieNode[] sons;
boolean isEnd;
TrieNode() {
sons = new TrieNode[NODE_SIZE];
}
/**
* 在当前节点开始插入字符
*
* @param str 需要插入的字符
*/
public void insert(String str) {
count++;
// String转为字符数组
TrieNode node = this;
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
char ch = chars[i];
//获取在子节点中的位置
int pos = ch - 'a';
TrieNode son = node.sons[pos];
if (son == null) {
son = new TrieNode();
son.value = ch;
node.sons[pos] = son;
}
son.count++;
node = son;
if (i == chars.length - 1) {
son.isEnd = true;
}
}
}
/**
* 从当前节点开始所搜以此关键词为前缀的单词有多少个
*
* @param keywords 关键词
* @return 个数
*/
public int search(String keywords) {
char[] chars = keywords.toCharArray();
TrieNode node = this;
for (char ch : chars) {
TrieNode son = node.sons[ch - 'a'];
if (son == null) {
return 0;
}
node = son;
}
return node.count;
}
/**
* 重写一下toString方法,查看字典树
*
* @return 字母顺序的字典树
*/
@Override
public String toString() {
List<String> strList = new ArrayList<>();
travel(this, strList);
return strList.toString();
}
public void travel(TrieNode node, List<String> strList) {
if (node.isEnd) {
strList.add(word.toString());
}
for (TrieNode son : node.sons) {
if (son != null) {
word.append(son.value);
travel(son, strList);
//回溯到原来的长度
word.deleteCharAt(word.length() - 1);
}
}
}
}
}
后面两天抽一天写下spring batch,最近有应用场景,记录下,顺便帮助下有需求的人,这东西中文文档太少,磕磕绊绊问题也太多了.