方法1 :O(N^2) 序列化
- 相同的子树会被序列化成相同的子串;
- 不同的子树会被序列化成不同的子串。
class Solution {
// <节点为root的树的序列化字符串,节点>
Map<String, TreeNode> map = new HashMap<>();
List<TreeNode> res = new ArrayList<>();
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
dfs(root);
return res;
}
// 返回当前树的序列化结果
public String dfs(TreeNode node) {
if (node == null) {
return "";
}
// 序列化当前树,这一步需要O(n)
String str = node.val +
"(" + dfs(node.left) + ")(" + dfs(node.right) + ")";
if (map.containsKey(str)) {
if (!res.contains(map.get(str))) {
res.add(map.get(str));
}
} else {
map.put(str, node);
}
return str;
}
}
方法2 :O(N) 序列化优化
- 对于每一个不同的子树,在DFS时可以生成唯一的序号idx
- 判断子树是否重复:因为自底向上遍历,所以对于每个节点,可以获得其左右子树对应的唯一序号idx,这个节点的val和其左右子树的序号idx可以生成字符串标志flag"root_val,left_idx,right_idx",如果字符串在哈希Map中出现过,就说明当前子树重复;如果字符串没在哈希Map中出现过,就将<字符串,未使用过的idx>存入哈希Map。
- 递归返回值:当前节点对应二叉树的唯一序号idx
如果dfs不返回idx而是返回当前子树字符串标志flag,而每个子树flag就从"root_val,left_idx,right_idx"变成了"root_val,left_flag,right_flag", 这样就是官方题解的方法1,每个字符串标志就是当前二叉树的序列化。第n个节点生成字符串的时间从O(1)变成O(n),那么总时间复杂度就变成了O(n2)
class Solution {
int index = 0; // index表示不同子树的数量
List<TreeNode> res = new ArrayList<>();
// <节点id,<节点,节点index>>
Map<String, Pair<TreeNode, Integer>> map = new HashMap<>();
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
dfs(root);
return res;
}
// dfs,返回一颗树的序号index,
public int dfs(TreeNode root) {
if (root == null) {
return 0;
}
// id用于标识一棵树,相同的树id一样
String id = root.val + "-" + dfs(root.left) + "-" + dfs(root.right);
// 遍历到了相同的树
if (map.containsKey(id)) {
TreeNode node = map.get(id).getKey();
if (!res.contains(node)) {
res.add(node);// 避免重复添加
}
return map.get(id).getValue(); // 返回index
} else {
index++;
map.put(id, new Pair<TreeNode, Integer>(root, index));
return index;
}
}
}