按下述要求实现 StreamChecker 类:
treamChecker streamChecker = new StreamChecker(["cd","f","kl"]); // 初始化字典 streamChecker.query('a'); // 返回 false streamChecker.query('b'); // 返回 false streamChecker.query('c'); // 返回 false streamChecker.query('d'); // 返回 true,因为 'cd' 在字词表中 streamChecker.query('e'); // 返回 false streamChecker.query('f'); // 返回 true,因为 'f' 在字词表中 streamChecker.query('g'); // 返回 false streamChecker.query('h'); // 返回 false streamChecker.query('i'); // 返回 false streamChecker.query('j'); // 返回 false streamChecker.query('k'); // 返回 false streamChecker.query('l'); // 返回 true,因为 'kl' 在字词表中。
提示:
1 <= words.length <= 2000
1 <= words[i].length <= 2000
字典树是一种查找树,说到查找我们很容易想起来map,接下来考虑一些情况,比如统计字符串,这些字符串有大量重复的前缀:“aaaaaapple","aaaapple","aaaamelon"..... 在数据量不大的时候我们能轻易的将它们存到map中,很方便。这时候突然要求统计前面包括4个a的字符串有多少,我们又需要将map中每个string拿出来,截前4个和aaaa比较是否相同,很不方便。
字典树,能够处理大量字符串,优点在于利用字符串的公共前缀,在存储时节约存储空间,并在查询时最大限度减少无谓的字符串比较。
根节点不包含字符,除根节点外每个子节点都包含一个字符
从根节点到某叶子节点,路径经过字符串联,就对应一个字符串。
下图展示了一个小字典树,包括字符串:”to","tea","ted","ten","a","inn"
最先想到的就是词频统计,给出一段字符,问某集合中有多少个字符串能够前缀匹配。
还有一个很经典的应用,在许多搜索框中,比如某些编辑器的控件搜索栏,我们输入前几个字符,它就会把包括这些字符的选项在下面显示出来。比如下面是vscode扩展中输入go,会自动显示go开头的选项。
字典树的节点结构不是一成不变的,字典树只是一个思想,根据不用的需求有不同的节点结构,比如我们只有查找的需求,就可以将树节点设置为下面这样:
// golang type TrieNode struct { flg bool children map[byte]*TrieNode }
// c++ struct TrieNode { bool flg; trieNode* children[26]; }
结构中的标志位来表示从根节点到该节点组成的字符串是否存在,比如只存有一个apple,只有在e节点flg为true。
children表示接下来的字符,上面写死了,对于golang可以把byte换成interface{}。
这时候又突然要求我们能够统计以某字符串为前缀的字符串有多少个,那么我们就得在struct中添加一个计数单位
type TrieNode struct { flg bool sum int children map[byte]*TrieNode }
对每个节点来说,只要在插入过程中经过该节点,该节点的sum就+1,比如插入apple,apple五个字符的sum都是1,之后再插入appple,前三个字符app的sum就是2,我们在查前缀的时候就可以顺着前缀找到前缀最后的字符,来看字典树中对应位置字符的sum,直接返回。
回到这道题。
第一步,很明显将words中的字符串保存为字典树。
第二步,要保存query的字符组成的字符串。
第三步,考虑到最后比较的时候不要做无谓的比较,记录一下书中最长的字符串长度,比较的时候从letter中最多取该长度的字符来比较。
第四步,考虑好上面三点,就能做出StreamCheker结构。
第五步,实现Query方法,查询逻辑很简单了。
之前用C++写过字典树,现在转golang了也就试着用go写,过程磕磕绊绊也算写完了,测试通过。
对外暴露的只有Constructor构造函数和Query查询方法。
type TrieNode struct { flg bool child map[byte]*TrieNode } func newTrieNode() *TrieNode { return &TrieNode{flg:false, child:make(map[byte]*TrieNode)} } type StreamChecker struct { root *TrieNode letter []byte maxLen int } func NewChecker() StreamChecker { return StreamChecker{root:newTrieNode(),letter:make([]byte,0),maxLen:0} } func reverse(c []byte) []byte { for i,j:=0,len(c)-1;i
sc.maxLen { sc.maxLen = len(v) } } return sc } func (this *StreamChecker) Query(letter byte) bool { this.letter = append(this.letter, letter) node := this.root for i,ml:= len(this.letter)-1,1;i>=0 && ml<=this.maxLen;i-- { _,ok := node.child[this.letter[i]] if !ok { return false } node = node.child[this.letter[i]] if node.flg { return true } } return node.flg }
参考资料:
1. https://blog.csdn.net/qq_31964727/article/details/80862805#3.%E5%AD%97%E5%85%B8%E6%A0%91%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF
2. https://www.cnblogs.com/xujian2014/p/5614724.html
3. https://www.cnblogs.com/TheRoadToTheGold/p/6290732.html
记录每天解决的小问题,积累起来去解决大问题