Find Duplicate Subtrees

MediumTrees · DFS · Hashing (Subtree IDs)🧩 Tree DFSLeetCode #652 ↗Updated

Problem

Given the root of a binary tree, find every subtree that occurs at least twice — two subtrees match when they have the same shape and the same value at every position. For each group of matching subtrees, return the root node of any one of them. The answer list may be in any order.
Inputroot = [5,4,6,3,null,4,null,null,null,3]Output[[3],[4,3]]
Inputroot = [2,1,1]Output[[1]]
Inputroot = [1,2,3]Output[]

1 ≤ nodes ≤ 5000  ·  -200 ≤ Node.val ≤ 200

Try it yourself

Java
class Solution {
    public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Give every distinct subtree a small integer ID, computed bottom-up from the triple (leftID, val, rightID). Equal subtrees get equal triples, so a hash map turns “are these subtrees identical?” into an O(1) lookup — report a node the moment its ID is seen for the second time.
PatternTree DFS

Instinct If the answer for a node can be built from the answers of its left and right subtrees, write one recursive function and trust it on the children.

Memory hack Ask the kids, then decide - each child reports up, the parent combines, and a global variable keeps the best "bend" seen so far.

Open the Tree DFS guide →

Approaches

Brute ForceCompare Every Pair of Subtrees

Time O(n³)Space O(n)

Collect all nodes into a list. For node i, compare its subtree structurally against every other node. Report i only if some later node matches it and no earlier node does, so each group is reported exactly once (by its first member).

Java
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
    List<TreeNode> all = new ArrayList<>();
    collect(root, all);
    List<TreeNode> res = new ArrayList<>();
    for (int i = 0; i < all.size(); i++) {
        boolean earlierCopy = false, laterCopy = false;
        for (int j = 0; j < all.size() && !earlierCopy; j++) {
            if (j == i || !same(all.get(i), all.get(j))) continue;
            if (j < i) earlierCopy = true; // the group was already reported
            else laterCopy = true;
        }
        if (laterCopy && !earlierCopy) res.add(all.get(i));
    }
    return res;
}
private void collect(TreeNode node, List<TreeNode> all) {
    if (node == null) return;
    all.add(node);
    collect(node.left, all);
    collect(node.right, all);
}
private boolean same(TreeNode a, TreeNode b) {
    if (a == null || b == null) return a == b;
    return a.val == b.val && same(a.left, b.left) && same(a.right, b.right);
}

Obviously correct and handy as a test oracle, but n² pairs each cost up to O(n).

At a glance

LevelApproachTimeSpace
Brute ForceCompare Every Pair of SubtreesO(n³)O(n)
BetterSerialize Every Subtree into a StringO(n²)O(n²)
OptimalInteger IDs from (left, val, right) TriplesO(n)O(n)
Common pitfalls
  • Ambiguous serialization (e.g. no null markers or separators) makes different trees collide.
  • Adding the node on every repeat instead of only when the count hits exactly 2 → duplicates in the answer.
  • Pre-order strings without null markers lose shape: [1,2] and [1,null,2] look equal.
Interview tips
  • Start with the serialization idea, then point out the O(n²) string cost and shrink keys to IDs.
  • Explain why post-order is required: a node's key depends on its children's keys.
  • Clarify that any one representative per group is enough and order does not matter.
Pattern
Subtree serialization / hashing → when you must detect equal or repeated subtrees, or compare tree structures quickly.
My notes saved in this browser