直接有道题教板子了。
1. LC 208 实现Trie(前缀树)
1.1. 数组写法
class Trie {
// 二维数组写法
static int size = (int) 1e5;
int index;
int[][] trie;
int[] count;
public Trie() {
index = 0;
trie = new int[size][26];
count = new int[size];
}
public void insert(String word) {
int p = 0;
for (int i = 0; i < word.length(); i++) {
int c = word.charAt(i) - 'a';
if(trie[p][c]==0) trie[p][c] = ++index; // 不能是index++,因为不可能出现在同一行,要换行了
p = trie[p][c];
}
count[p]++; // 以该索引结尾的字符串数量赠一
}
public boolean search(String word) {
int p = 0;
for (int i = 0; i < word.length(); i++) {
int c = word.charAt(i) - 'a';
if(trie[p][c]==0) return false;
p = trie[p][c];
}
return count[p]>0;
}
public boolean startsWith(String prefix) {
int p = 0;
for (int i = 0; i < prefix.length(); i++) {
int c = prefix.charAt(i) - 'a';
if(trie[p][c]==0) return false;
p = trie[p][c];
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
我个人不喜欢这种。没有树的感觉。
1.2. 节点写法
class Trie {
// 节点写法
static class TrieNode{
boolean end; // this node is the end of some inserted string?
TrieNode[] tns = new TrieNode[26]; // the successor node has 26 choices since the character set of a string has only 26 elements
}
TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode p = root;
for (int i = 0; i < word.length(); i++) {
int c = word.charAt(i) - 'a';
if(p.tns[c]==null) p.tns[c] = new TrieNode();
p = p.tns[c];
}
p.end = true;
}
public boolean search(String word) {
TrieNode p = root;
for (int i = 0; i < word.length(); i++) {
int c = word.charAt(i) - 'a';
if(p.tns[c]==null) return false;
p = p.tns[c];
}
return p.end;
}
public boolean startsWith(String prefix) {
TrieNode p = root;
for (int i = 0; i < prefix.length(); i++) {
int c = prefix.charAt(i) - 'a';
if(p.tns[c]==null) return false;
p = p.tns[c];
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
对味了。
2. LC 3045 统计前后缀下标对Ⅱ
Trie而不板的一道题,实际上很有思维量:如何判断既是前缀也是后缀?
把一个字符串转换为字符对列表,其中pair(i) = ( s(i) , s(n-i-1) ),下标从0开始算。例如:
str1 = abc
str2 = abczabc
转换为
p1 = (a,c) (b,b) (c,a)
p2 = (a,c) (b,b) (c,a) (z,z) (a,c) (b,b) (c,a)
那么如果p1的所有对是p2的所有对的前缀,st1就既是str2的前缀,也是它的后缀。
直观来讲的话,p1(i) == p2(i) 意味着,str1的第i个字符和str2的第i个字符相等,1且str1的倒数第i个字符和str2的倒数第i个字符相等。如果对于任意的i这句话成立,那么显然既是前缀又是后缀。
所以这道题就从判断前后缀转换成了判断前缀。判断前缀怎么做,就变成了字典树板子。但这题主要难点在于能想到通过pair对来转换为字典树板子。
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
class pair{
int x,y;
public pair(int x,int y){
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object p){
if(p == null){
return false;
}
if(this.getClass()!=p.getClass()){
return false;
}
pair tmp = (pair) p;
return tmp.x==x && tmp.y==y;
}
@Override
public int hashCode(){
return Objects.hash(x,y);
}
}
class Node{
Map<pair,Node> son = new HashMap<>();
int cnt; // cnt仅在字符串尾部元素处计数,本质上是对该字符串出现次数的计数
}
class Solution {
public long countPrefixSuffixPairs(String[] words) {
long ans = 0L;
int len;
Node root = new Node(); // 对应空字符串
Node cur;
for (String word : words) {
char[] ch = word.toCharArray();
len = ch.length;
cur = root;
for (int i = 0; i < len; i++) {
pair p = new pair(ch[i], ch[len - 1 - i]);
// 深搜到字符串尾,没有就插入
cur = cur.son.computeIfAbsent(p, pair -> new Node());
// 途径的每个字符串都是前缀,加上计数
ans += cur.cnt;
}
// 当前字符串计数增一
cur.cnt++;
}
return ans;
}
}
这里字典树的写法参考的灵神的,我之前板刷的一个写法太繁了。灵神在key上对pair对进行了压缩,我没压缩穿了个数对进去,还得覆写hashCode和equals,java是真的笨。
复杂度方面,hash表最快O(1),每个字符串都遍历了一遍,所以是O(∑(i=0:words.length-1)(words[i].length())。
3. LC 100268 最长公共后缀查询
周赛390T4,AK的第二把。
这题就是很板的字典树。只要稍微改下ds,对于每个节点都记录下能到达的wordsContainer索引就可以了,用小根堆记录。
import java.util.*;
class Trie{
String[] wordsContainer;
public Trie(String[] wordsContainer){
this.wordsContainer = wordsContainer;
}
PriorityQueue<Integer> index = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
int l1 = wordsContainer[o1].length();
int l2 = wordsContainer[o2].length();
if(l1==l2){
return Integer.compare(o1,o2);
}
return Integer.compare(l1,l2);
}
});
Map<Character,Trie> child = new HashMap<>();
}
class Solution {
public int[] stringIndices(String[] wordsContainer, String[] wordsQuery) {
Trie root = new Trie(wordsContainer);
for (int i = 0; i < wordsContainer.length; i++) {
String s = wordsContainer[i];
char[] ch = s.toCharArray();
Trie cur = root;
root.index.offer(i);
for (int j = ch.length - 1; j >= 0; j--) {
char c = ch[j];
Trie child = cur.child.get(c);
if(child==null){
cur.child.put(c,new Trie(wordsContainer));
}
cur = cur.child.get(c);
cur.index.offer(i);
}
}
int[] ans = new int[wordsQuery.length];
outer:for (int i = 0; i < wordsQuery.length; i++) {
String s = wordsQuery[i];
char[] ch = s.toCharArray();
Trie cur = root;
for (int j = ch.length - 1; j >= 0; j--) {
char c = ch[j];
Trie tmp = cur.child.get(c);
if(tmp==null){
ans[i] = cur.index.peek();
continue outer;
}
cur = tmp;
}
ans[i] = cur.index.peek();
}
return ans;
}
}