Start learning free

LESSON 328 OF 414 · BINARY TREES

Symmetric Tree in Scala: Recursive Mirror Check

Practice O(n) Read the full explanation

Given the root of a binary tree, check whether it is a mirror of itself (i.e., symmetric around its center).

 

Example 1:

Input: root = [1,2,2,3,4,4,3]
Output: true

Example 2:

Input: root = [1,2,2,null,3,null,3]
Output: false

 

Constraints:

  • The number of nodes in the tree is in the range [1, 1000].
  • Each node value is between -100 and 100.

 

Follow up: Could you solve it both recursively and iteratively?
STEP 1 OF 7
    Ctrl + Enter to check

    SCALA · BINARY TREES

    Symmetric Tree in Scala, explained

    Problem overview

    The method isSymmetric receives the root of a binary tree and returns true when the tree is a mirror image of itself around its center. The shape and the values must both match.

    The tree with root 1, children 2 and 2, and grandchildren 3, 4 under the left 2 and 4, 3 under the right 2 is symmetric: the right half is the left half reflected, with 3 and 4 swapped in position. The tree with root 1 and two children 2 whose only children are a right child 3 on each side is not symmetric. A mirror would need the left 2 to have its child on the left, because the right 2 has it on the right.

    Approach: compare two subtrees at once

    Two trees are mirrors of each other when their roots hold equal values, the left subtree of the first mirrors the right subtree of the second, and the right subtree of the first mirrors the left subtree of the second. That rule is recursive, and it also defines symmetry: a tree is symmetric when it mirrors itself.

    So the helper dfs(root1, root2) takes two nodes that should be mirror images. It looks at a pair, and then it recurses into two more pairs: the outer pair, which is root1.left with root2.right, and the inner pair, which is root1.right with root2.left. The main method starts with the tree and a second walk through the same tree: dfs(root, root). Walking the same tree twice works because the two walks go in opposite directions, left on one side while right on the other.

    Step by step

    1. Start the check by returning dfs(root, root) from isSymmetric.
    2. Declare the helper above it, with two TreeNode parameters, root1 and root2, and the result type Boolean.
    3. Both empty. If root1 == null && root2 == null, return true. Two empty subtrees mirror each other. This is the base case for the leaves.
    4. Exactly one empty. If root1 == null || root2 == null, return false. The previous check removed the case of both being empty, so here only one of them is, and the shapes differ. The order of these two checks matters.
    5. Different values. If root1.value != root2.value, return false. Both nodes exist at this point, so reading their values is safe.
    6. Outer pair. If dfs(root1.left, root2.right) is false, return false at once. The inner pair no longer matters.
    7. Inner pair. Return dfs(root1.right, root2.left). If the outer pair matched, this call decides the answer.

    The solution in Scala

    class Solution {
        def dfs(root1: TreeNode, root2: TreeNode): Boolean = {
            if (root1 == null && root2 == null) {
                return true
            }
            if (root1 == null || root2 == null) {
                return false
            }
            if (root1.value != root2.value) {
                return false
            }
            if (!dfs(root1.left, root2.right)) {
                return false
            }
            return dfs(root1.right, root2.left)
        }
    
        def isSymmetric(root: TreeNode): Boolean = {
            return dfs(root, root)
        }
    }
    

    Scala details:

    • null and ==. In Scala, == on references is a null-safe value comparison, so root1 == null is fine even when root1 is null. Reading root1.value on a null, by contrast, throws a NullPointerException, which is why the null checks come first.
    • && and || short-circuit. The right-hand operand is evaluated only when it is needed. In root1 == null || root2 == null, the second test runs only when the first is false.
    • return is optional on the last line of a method. The early returns inside the if blocks are needed, as they leave the method before the later lines. They are at the method's own level and not inside a closure, so they behave as in any language.
    • A Boolean method. A method with a declared result type Boolean must give a Boolean on every path. Here every branch returns one.
    • Negation. !dfs(...) negates the call's result.

    Complexity

    Time: O(n). Each call compares one pair of positions and does constant work. A node can be the first argument of at most one call and the second argument of at most one call, since the two walks descend together, so the number of calls is at most about 2n plus the calls on missing children. A mismatch ends the work early.

    Space: O(h), where h is the height of the tree, for the recursion. A chain of nodes is a worst case, and a perfectly balanced tree has about log n levels. With the lesson's limit of 1,000 nodes the recursion is never a problem.

    Edge cases and common mistakes

    • A single node is symmetric: dfs(root, root) compares the node with itself and then two empty pairs.
    • A node with one child is not symmetric: the root 1 with only a left child 2 compares that child with a missing right child and fails the one-sided check.
    • Different values. A tree with root 1 and children 2 and 3 fails on the values of the first pair.
    • Same values in the wrong place. Comparing root1.left with root2.left instead of root2.right would test whether two trees are identical, not mirrored. The outer and inner pairs are the heart of the solution.
    • Checking the values of only the first level. A symmetric-looking top can hide a different shape lower down, as in the second example above, where the 3s are on the same side.
    • Swapping the two null checks. If the "one is null" test came first, two empty subtrees would be reported as different.

    Testing

    • Root 1 with children 2, 2 and grandchildren 3, 4 / 4, 3: true.
    • Root 1 with children 2, 2 that each have only a right child 3: false.
    • A single node: true.
    • Root 1 with children 2 and 3: false.
    • Root 1 with one child: false.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free