题目描述:
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
需要以列表的形式返回上述重复子树的根结点。
算法思想:
所有的二叉树问题,都逃不过那三个遍历,首先思考我们的问题属于三者中的哪一种。
首先,对于某一个节点,它应该做什么?
假如我现在站在这个2节点上,想知道以自己为根的子树是不是重复的,该不该加入重复列表中。
那么我要知道两个问题:
- 以我为根的子树长啥样
- 其他节点为根的子树长啥样
怎么知道自己啥样呢,如果我知道我的左右他们啥样,再加上我自己,不就是整棵树的样子了嘛,所以此题用的是三种遍历中的后序遍历,类似与后序求一棵树的节点,我们给出方法(注:描述一颗树的形状采用序列化)
String traverse(TreeNode root) {
// 对于空节点,可以用一个特殊字符表示
if (root == null) {
return "#";
}
// 将左右子树序列化成字符串
String left = traverse(root.left);
String right = traverse(root.right);
/* 后序遍历代码位置 */
// 左右子树加上自己,就是以自己为根的二叉树序列化结果
String subTree = left + "," + right + "," + root.val;
return subTree;
}
那么第二个问题,怎么知道其他节点长啥样呢?
用备忘录呗,把他们都记下来,统计统计出现的次数,不就知道哪些重复了嘛
代码实现:
class Solution {
HashMap<String,Integer> memo = new HashMap<>(); //存储所有已经看过的树
LinkedList<TreeNode> res = new LinkedList<>(); //存储重复子树
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
traverse(root);
return res;
}
//描述一棵树的样子 序列化
public String traverse(TreeNode root){
//使用#表示空节点
if(root == null) return "#";
//先知道它的左子树啥样
String left = traverse(root.left);
//再知道它的右子树啥样
String right = traverse(root.right);
//于是我们就知道这棵树啥样了
String subTree = left + "," + right + "," + root.val;
//看看备忘录里面有没有这个节点
int freq = memo.getOrDefault(subTree, 0); //Map的getOrDefault方法,如果不存在这个键值,默认为0
//只在出现第一次重复的时候加一次
if(freq == 1) {
res.add(root);
}
memo.put(subTree, freq+1);
return subTree;
}
}