字典树
CPU爆炸
理论基础
-
Trie 树(又叫「前缀树」或「字典树」)是一种用于快速查询「某个字符串/字符前缀」是否存在的数据结构。
-
其核心是使用「边」来代表有无字符,使用「点」来记录是否为「单词结尾」以及「其后续字符串的字符是什么」。
-
应用:
- 在纯算法领域,前缀树算是一种较为常用的数据结构。
- 不过如果在工程中,不考虑前缀匹配的话,基本上使用 hash 就能满足。如果考虑前缀匹配的话,工程也不会使用 Trie 。一方面是字符集大小不好确定,另外,对于个别的超长字符 Trie 会进一步变深。
-
模板:添加字符串至字典树,查询时返回次数
-
二维数组
- 使用二维数组
trie[]
来存储我们所有的单词字符。 - 使用
index
来自增记录我们到底用了多少个格子(相当于给被用到格子进行编号)。 - 使用
count[]
数组记录某个格子被「被标记为结尾的次数」(当idx
编号的格子被标记了n
次,则有count[idx]=n
)。
class Trie { int N = 100009; // N为节点个数,直接设置为十万级 int[][] trie; int[] count; int index; public Trie() { trie = new int[N][26]; count = new int[N]; index = 0; } public void insert(String s) {// 将字符串str添加进字典树 int p = 0; for (int i = 0; i < s.length(); i++) { int u = s.charAt(i) - 'a'; if (trie[p][u] == 0) trie[p][u] = ++index;// 创建结点并赋予编号index p = trie[p][u];// 走到下一个结点 } count[p]++;// 计数 } public int search(String s) {// 返回当前字符串出现的次数 int p = 0; for (int i = 0; i < s.length(); i++) { int u = s.charAt(i) - 'a'; if (trie[p][u] == 0) return false;//若当前结点不存在,那么直接返回0 p = trie[p][u]; } return count[p]; } }
- 复杂度
- 时间复杂度: O ( l e n ) O(len) O(len), l e n len len为入参字符串长度
- 空间复杂度: O ( n k ) O(nk) O(nk), n n n为节点数量, k k k为字符集大小
- 使用二维数组
-
动态扩点
建立
TrieNode
结构节点。class Trie { class TrieNode { int cnt; TrieNode[] tns = new TrieNode[26]; } TrieNode root; public Trie() { root = new TrieNode(); } public void insert(String s) { TrieNode p = root; for(int i = 0; i < s.length(); i++) { int u = s.charAt(i) - 'a'; if (p.tns[u] == null) p.tns[u] = new TrieNode();//创建结点 p = p.tns[u]; } p.cnt++; } public int search(String s) { TrieNode p = root; for(int i = 0; i < s.length(); i++) { int u = s.charAt(i) - 'a'; if (p.tns[u] == null) return 0; p = p.tns[u]; } return p.cnt; } }
- 复杂度
- 时间复杂度: O ( l e n ) O(len) O(len), l e n len len为入参字符串长度
- 空间复杂度: O ( n k ) O(nk) O(nk), n n n为节点数量, k k k为字符集大小
- 复杂度
-
静态数组
减小空间复杂度,并避免垃圾回收
class Trie { // 以下 static 成员独一份,被创建的多个 Trie 共用 static int N = 100009; // 直接设置为十万级 static int[][] trie = new int[N][26]; static int[] count = new int[N]; static int index = 0; // 在构造方法中完成重置 static 成员数组的操作 // 这样做的目的是为减少 new 操作(无论有多少测试数据,上述 static 成员只会被 new 一次) public Trie() { for (int row = index; row >= 0; row--) { Arrays.fill(trie[row], 0); } Arrays.fill(count, 0); index = 0; } public void insert(String s) { int p = 0; for (int i = 0; i < s.length(); i++) { int u = s.charAt(i) - 'a'; if (trie[p][u] == 0) trie[p][u] = ++index; p = trie[p][u]; } count[p]++; } public int search(String s) { int p = 0; for (int i = 0; i < s.length(); i++) { int u = s.charAt(i) - 'a'; if (trie[p][u] == 0) return 0; p = trie[p][u]; } return count[p]; } }
- 复杂度
- 时间复杂度: O ( l e n ) O(len) O(len), l e n len len为入参字符串长度
- 空间复杂度: O ( n k ) O(nk) O(nk), n n n为节点数量, k k k为字符集大小
- 复杂度
-
相关题目
统计异或值在范围内的数对有多少【LC1803】
Given a (0-indexed) integer array
nums
and two integerslow
andhigh
, return the number of nice pairs.A nice pair is a pair
(i, j)
where0 <= i < j < nums.length
andlow <= (nums[i] XOR nums[j]) <= high
.
字典树
-
思路:
-
首先使用字典树存储数组中元素的二进制形式,由于 n u m s [ i ] ≤ 2 ∗ 1 0 4 nums[i] \le 2*10^4 nums[i]≤2∗104,因此用15位二进制就可以表示;
-
由容斥原理可得,异或值在 [ l o w , h i g h ] [low,high] [low,high]之间的对数=异或值为 h i g h high high的对数-异或值为 l o w − 1 low-1 low−1的对数
-
然后求出 n u m s [ i ] nums[i] nums[i]异或 n u m s [ 0 , i − 1 ] nums[0,i-1] nums[0,i−1]小于等于 t a r g e t target target的数量,再将 n u m s [ i ] nums[i] nums[i]加入字典树中
具体步骤:从高位开始枚举,符合则计数,不符合直接return
- 如果 t a r g e t target target的第 j j j位为1,那么与之前数字第 j j j位异或结果可以为1也可以为0
- 如果 t a r g e t target target的第 j j j位为1,那么与之前数字第 j j j位异或结果只能为0
-
-
二维数组实现
class Solution { int[][] trie; int[] cnt; int idx; public int countPairs(int[] nums, int low, int high) { trie = new int[nums.length * 16][2]; cnt = new int[nums.length * 16]; return get(nums, high) - get(nums, low - 1); } int get(int[] nums, int high) { idx = 0; for (int i = 0; i < trie.length; i++) trie[i][0] = trie[i][1] = cnt[i] = 0; int ans = 0; for (int i = 0; i < nums.length; i++) { ans += query(nums[i], high); add(nums[i]); } return ans; } void add(int x) { int p = 0; for (int i = 14; i >= 0; i--) { int u = (x >> i) & 1; if (trie[p][u] == 0) trie[p][u] = ++idx; p = trie[p][u]; //移动到下一个结点 cnt[p]++; // 个数增加,cnt[x]代表x结点出现的次数 } } int query(int x, int high) { int sum = 0, p = 0; for (int i = 14; i >= 0; i--) { int u = (x >> i) & 1; if (((high >> i) & 1) == 1) { //high当前i位为1, 那么x与以前数当前i位的异或可以位1或者0 sum += cnt[trie[p][u]];//加上与x异或后当前i位为0的数量 if (trie[p][u ^ 1] == 0) return sum; //没有结点可以继续走下去,直接返回 p = trie[p][u ^ 1]; //继续往异或的结点走下去 } else { //high当前i位为0, x与以前数异或的第i为必须为0 if (trie[p][u] == 0) return sum; //没有结点走下去 p = trie[p][u]; //寻找与x的第i位相同的进制,异或结果为0 } } sum += cnt[p]; //加上走到最后的结点数 return sum; } } 作者:Tizzi 链接:https://leetcode.cn/problems/count-pairs-with-xor-in-a-range/solutions/2045650/javac-zi-dian-shu-fu-zi-dian-shu-mo-ban-566um/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 复杂度
- 时间复杂度: O ( n l o g C ) O(nlogC) O(nlogC)
- 空间复杂度: O ( n l o g C ) O(nlogC) O(nlogC), n n n为节点数量, C C C为字符集大小
- 复杂度
-
TrieNode
class Solution { class TrieNode{ TrieNode[] next = new TrieNode[2]; int cnt; } TrieNode root; public int countPairs(int[] nums, int low, int high) { return get(nums, high) - get(nums, low - 1); } public int get(int[] nums, int t){ root = new TrieNode(); int ans = 0; for (int i = 0; i < nums.length; i++){ ans += search(nums[i], t); add(nums[i]); } return ans; } public void add(int num){ TrieNode p = root; for (int i = 14; i >= 0; i--){ // 高位至低位 int u = (num >> i) & 1; if (p.next[u] == null) p.next[u] = new TrieNode(); p = p.next[u]; p.cnt++; } } public int search(int num, int t){ int count = 0; TrieNode p = root; for (int i = 14; i >= 0; i--){ int u = (num >> i) & 1; if (((t >> i) & 1) == 1){// t的第i位为1 if (p.next[u] != null) count += p.next[u].cnt;// 结果为0 之后节点为任意值均符合条件 直接加cnt if (p.next[u ^ 1] == null) return count;// 之后没有节点可以走了 直接返回结果 p = p.next[u ^ 1];// t的第i位为0 }else{ if (p.next[u] == null) return count; p = p.next[u]; } } count += p.cnt; return count; } }
- 复杂度
- 时间复杂度: O ( n l o g C ) O(nlogC) O(nlogC)
- 空间复杂度: O ( n l o g C ) O(nlogC) O(nlogC), n n n为节点数量, C C C为字符集大小
- 复杂度
*哈希表
-
思路:
- 基于异或性质
x
⊕
y
=
t
x \oplus y = t
x⊕y=t等价于$y = t \oplus x $,可以统计
nums
中每个数的出现次数,并记录在哈希表cnt
中 - 然后遍历
cnt
的每一个键 x x x,那么 c n t [ x ] ∗ c n t [ x ⊕ t ] cnt[x]*cnt[x\oplus t] cnt[x]∗cnt[x⊕t],累加结果除以2即为数组中任意两数异或结果为 y y y的对数 - 那么对区间 [ l o w , h i g h ] [low,high] [low,high]的每个数都这样统计即可得最终结果。
- 基于异或性质
x
⊕
y
=
t
x \oplus y = t
x⊕y=t等价于$y = t \oplus x $,可以统计
-
实现[超时]
class Solution { public int countPairs(int[] nums, int low, int high) { Map<Integer, Integer> numToCount = new HashMap<>(); for (int num : nums){ numToCount.put(num, numToCount.getOrDefault(num, 0) + 1); } int count = 0; for (int i = low; i <= high; i++){ for (var node: numToCount.entrySet()){ count += node.getValue() * numToCount.getOrDefault(node.getKey() ^ i,0); } } return count / 2; } }
- 复杂度
- 时间复杂度: O ( n ∗ C ) O(n*C) O(n∗C), n n n为数组长度, C C C为 h i g h − l o w high-low high−low
- 空间复杂度: O ( n ) O(n) O(n)
- 复杂度
-
优化:
-
将 [ 0 , t ] [0,t] [0,t]分成若干区间,计算每个区间的答案[没咋懂]
- 若右数第m位(注意
m>1
)是1, 则可划分出一组只需考虑第m位及以左; - 特别地, 若右边从第1位开始连续m位是1, 则有一组, 只需考虑第
m+1
位及以左. (例如若 x=10011, 最后一组忽略右2位) 统一来看, 则可通过 x+1 的 第m位(m>=1)为条件进行判断, 计算异或时记得减一
- 若右数第m位(注意
-
基于容斥原理,把 [ l o w , h i g h ] [low,high] [low,high]转化为计算 [ 0 , h i g h ] [0,high] [0,high]和 [ 0 , l o w − 1 ] [0,low-1] [0,low−1]相减的结果
class Solution { public int countPairs(int[] nums, int low, int high) { int ans = 0; var cnt = new HashMap<Integer, Integer>(); for (int x : nums) cnt.put(x, cnt.getOrDefault(x, 0) + 1); for (++high; high > 0; high >>= 1, low >>= 1) { var nxt = new HashMap<Integer, Integer>(); for (var e : cnt.entrySet()) { int x = e.getKey(), c = e.getValue(); if ((high & 1) == 1) ans += c * cnt.getOrDefault(x ^ (high - 1), 0); if ((low & 1) == 1) ans -= c * cnt.getOrDefault(x ^ (low - 1), 0); nxt.put(x >> 1, nxt.getOrDefault(x >> 1, 0) + c); } cnt = nxt; } return ans / 2; } } 作者:灵茶山艾府 链接:https://leetcode.cn/problems/count-pairs-with-xor-in-a-range/solutions/2045560/bu-hui-zi-dian-shu-zhi-yong-ha-xi-biao-y-p2pu/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 复杂度
- 时间复杂度: O ( n ) O(n) O(n),严格来说为 O ( n + n l o g U n ) O(n+nlog{\frac{U}{n}}) O(n+nlognU), n n n为数组长度, U U U为数组中的最大值
- 空间复杂度: O ( n ) O(n) O(n)
-