𝑰’𝒎 𝒉𝒉𝒈, 𝑰 𝒂𝒎 𝒂 𝒈𝒓𝒂𝒅𝒖𝒂𝒕𝒆 𝒔𝒕𝒖𝒅𝒆𝒏𝒕 𝒇𝒓𝒐𝒎 𝑵𝒂𝒏𝒋𝒊𝒏𝒈, 𝑪𝒉𝒊𝒏𝒂.
核心思路就是每遍历一个Root节点就和SubRoot进行树与树之间的比较,比较容易想到,代码如下:
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) {
return false;
}
return check(root, subRoot) || isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
public boolean check(TreeNode p, TreeNode q) {
if (p == null && q == null) {
return true;
}
if (p == null || q == null || p.val != q.val) {
return false;
}
return check(p.left, q.left) && check(p.right, q.right);
}
核心思路就是像官方题解里面的那样。
他用的是KMP的做法,我用的是String,因为String正好有contains这个方法可以用起来,因为主要考察的是思路吧,这个串匹配应该不是重点。代码如下:
public static void getString(TreeNode root, StringBuilder r) {
if (root == null) {
return;
}
r.append(root.val).append(",");
if (root.left != null) {
getString(root.left, r);
} else {
r.append("n,");
}
if (root.right != null) {
getString(root.right, r);
} else {
r.append("n,");
}
}
public static boolean isSubtree2(TreeNode root, TreeNode subRoot) {
StringBuilder r = new StringBuilder(), s = new StringBuilder();
getString(root, r);
getString(subRoot, s);
return r.toString().contains(s.toString());
}
要注意这里为什么加”,“,因为比如12n和2n,两个单节点的树,从string上看这里12.contains(2)是true,但事实上,并不是子树,所以要特别挑出这一点。
这个比较牛,首先得会欧拉筛选法,我也没听说过,于是我找了一个学习视频,几分钟学习了下核心思想:B站视频
核心思想如下:
两个数组
主角一共有两个x和y,x代表从2开始的没有被筛掉的自然数,y代表prime[i],也就是素数数组。
首先,x=2,从sifted数组中查看2这个数字有没有被筛掉,sifited[x]是false,意思就是没有被筛掉,作为素数加入到prime[]数组中。
y从prime[0]开始,也就是从第一个素数开始检查,看x是否能整除y。
public static int[] oula(int n) {
boolean[] sifted = new boolean[n + 1]; // 代表1~n个数字 true代表被筛掉了,false代表没有被筛掉
int[] prime = new int[n]; // 素数数组
int point = 0; // 素数的指针
for (int x = 2; x <= n; x++) {
if (!sifted[x]) { // 如果没有被筛掉
prime[point++] = x;
}
for (int j = 0; j <= point; j++) { // 遍历素数数组,通过y值进行筛选,被筛选掉的要做记录,退出条件是x能整除y<=>x%y==0
int y = prime[j];
if (x * y > n) { // 当x*y是,比n大的数,没必要筛选,因为不在考虑范围之内。
break;
}
sifted[y * x] = true;
if (x % y == 0) {
break;
}
}
}
return prime;
}
但是,这个里面并没有解释为什么要用素数哈,我的理解是素数之间做乘积运算,对hash值的碰撞比较少,也就是出现重复hash值的情况会降低。
之后再按照公式那么计算就好了,计算的时候,还需要再对代码进行理解一下:
public boolean isSubtree3(TreeNode root, TreeNode subRoot) {
int n = 100000;
int[] prime = oula(n);
int mode = 70000;
Map<TreeNode, int[]> rootMap = new HashMap<>();
Map<TreeNode, int[]> subRootMap = new HashMap<>();
dfs(root, rootMap, prime, mode);
dfs(subRoot, subRootMap, prime, mode);
int subRootHash = subRootMap.get(subRoot)[0];
for (Map.Entry<TreeNode, int[]> entry : rootMap.entrySet()) {
if (entry.getValue()[0] == subRootHash) {
return true;
}
}
return false;
}
public void dfs(TreeNode node, Map<TreeNode, int[]> map, int[] prime, int mod) {
if (node == null) {
return;
}
map.put(node, new int[]{node.val, 1});
dfs(node.left, map, prime, mod);
dfs(node.right, map, prime, mod);
if (node.left != null) {
int[] val = map.get(node);
val[1] += map.get(node.left)[1];
val[0] = (int) (val[0] + 31L * map.get(node.left)[0] * prime[map.get(node.left)[1]]) % mod;
}
if (node.right != null) {
int[] val = map.get(node);
val[1] += map.get(node.right)[1];
val[0] = (int) (val[0] + 179L * map.get(node.right)[0] * prime[map.get(node.right)[1]]) % mod;
}
}
其他,照着官方详解看,应该没有什么太大的压力了。主要收获就是这里的求素数的方法,以后再做素数的时候,不要再用传统的1~ n \sqrt[]{n} n,挨个判断的做法了。