Facebook Pixel

1261. Find Elements in a Contaminated Binary Tree

Problem Description

You are given a binary tree that follows specific value assignment rules:

  • The root node has value 0
  • For any node with value x:
    • Its left child (if exists) has value 2 * x + 1
    • Its right child (if exists) has value 2 * x + 2

However, the tree has been contaminated - all node values have been changed to -1.

Your task is to implement a FindElements class with two methods:

  1. Constructor FindElements(root): Takes the contaminated binary tree and recovers all node values according to the rules above.

  2. Method find(target): Returns true if the target value exists in the recovered tree, false otherwise.

For example, if we have a tree structure:

       0
      / \
     1   2
    / \
   3   4

After contamination, all values become -1. The FindElements constructor should restore the original values based on the rules, and find(3) would return true while find(5) would return false.

The solution uses DFS to traverse the tree and restore values starting from the root (setting it to 0), then recursively calculates child values using the formulas 2 * x + 1 for left children and 2 * x + 2 for right children. All recovered values are stored in a hash set for O(1) lookup during find operations.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: A binary tree is a special type of graph where each node has at most two children and there are no cycles.

Is it a tree?

  • Yes: The problem explicitly states we're working with a binary tree structure.

DFS

  • We arrive at DFS: Since we're dealing with a tree structure, the flowchart directly leads us to use Depth-First Search.

Why DFS is appropriate for this problem:

  • We need to traverse the entire tree to recover all node values from their contaminated state (-1)
  • DFS allows us to process each node and recursively update its children's values based on the parent-child relationship (left child = 2parent + 1, right child = 2parent + 2)
  • The recursive nature of DFS perfectly matches the recursive structure of the tree and the value assignment rules
  • We can recover values in a single traversal, storing them in a hash set for O(1) lookup during find operations

Conclusion: The flowchart correctly identifies that for a tree-based problem like "Find Elements in a Contaminated Binary Tree," DFS is the optimal approach. The solution uses DFS to traverse from the root (setting it to 0), recursively calculate and assign values to children nodes, and store all recovered values in a set for efficient searching.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that even though all values are contaminated to -1, the tree structure itself remains intact. The parent-child relationships are preserved, which means we can reconstruct the original values by following the given rules.

Starting from what we know for certain - the root must be 0 - we can work our way down the tree. Once we know a parent's value, we can deterministically calculate its children's values using the formulas 2 * parent_value + 1 for the left child and 2 * parent_value + 2 for the right child.

This naturally suggests a traversal approach where we:

  1. Fix the root value to 0
  2. Visit each node and calculate its children's values based on the current node's value
  3. Continue this process recursively through the entire tree

Since we need to check if values exist in the tree later, we should store all recovered values during our traversal. A hash set is perfect for this - it gives us O(1) lookup time for the find operation.

The beauty of this approach is that we only need to traverse the tree once during initialization to recover all values. After that, finding any target value becomes a simple set membership check. The contamination is just a distraction - the tree's structure tells us everything we need to know to restore the original values.

Learn more about Tree, Depth-First Search, Breadth-First Search and Binary Tree patterns.

Solution Approach

The implementation uses DFS with a hash table to efficiently recover and store all node values.

Data Structures Used:

  • Set (self.s): Stores all recovered node values for O(1) lookup during find operations
  • Tree traversal: Uses the existing tree structure to navigate through nodes

Implementation Details:

  1. Constructor (__init__):

    • First, we manually set the root value to 0 since we know this is always the starting value
    • Initialize an empty set self.s to store all recovered values
    • Call the DFS helper function starting from the root
  2. DFS Helper Function:

    • Add the current node's value to the set
    • If a left child exists:
      • Calculate its value as current_value * 2 + 1
      • Recursively process the left subtree
    • If a right child exists:
      • Calculate its value as current_value * 2 + 2
      • Recursively process the right subtree
  3. Find Method:

    • Simply check if the target value exists in our set
    • Returns True if found, False otherwise

Why This Works:

  • The DFS traversal ensures we visit every node exactly once
  • As we traverse, we recover each node's value based on its parent's value
  • By storing values in a set during traversal, we avoid the need to traverse the tree again for each find operation
  • The in-place modification of node values means we're actually recovering the tree structure, not just collecting values

Time Complexity:

  • Initialization: O(n) where n is the number of nodes (we visit each node once)
  • Find operation: O(1) due to hash set lookup

Space Complexity:

  • O(n) for storing all node values in the set
  • O(h) for the recursion stack where h is the height of the tree

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small contaminated tree to see how the solution recovers the values:

Initial contaminated tree:

       -1
      /  \
    -1    -1
    /
  -1

Step 1: Initialize FindElements

  • Set root.val = 0 (we know root is always 0)
  • Create empty set s = {}
  • Start DFS from root

Step 2: DFS Traversal

Processing root (value = 0):

  • Add 0 to set: s = {0}
  • Root has left child, calculate: 2 * 0 + 1 = 1
  • Set left child.val = 1
  • Root has right child, calculate: 2 * 0 + 2 = 2
  • Set right child.val = 2
  • Recursively process children

Processing left child (value = 1):

  • Add 1 to set: s = {0, 1}
  • This node has left child, calculate: 2 * 1 + 1 = 3
  • Set its left child.val = 3
  • No right child, skip
  • Recursively process left child

Processing node with value 3:

  • Add 3 to set: s = {0, 1, 3}
  • No children, return

Processing right child (value = 2):

  • Add 2 to set: s = {0, 1, 2, 3}
  • No children, return

Final recovered tree:

       0
      / \
     1   2
    /
   3

Step 3: Find operations

  • find(1): Check if 1 in {0, 1, 2, 3} → returns True
  • find(5): Check if 5 in {0, 1, 2, 3} → returns False
  • find(0): Check if 0 in {0, 1, 2, 3} → returns True

The key insight is that we recover all values in one pass during initialization, then finding becomes a simple O(1) set lookup.

Solution Implementation

1# Definition for a binary tree node.
2# class TreeNode:
3#     def __init__(self, val=0, left=None, right=None):
4#         self.val = val
5#         self.left = left
6#         self.right = right
7
8class FindElements:
9    """
10    A class to recover a contaminated binary tree and provide element lookup.
11    The tree recovery follows the rule:
12    - Root value is 0
13    - Left child value = parent_value * 2 + 1
14    - Right child value = parent_value * 2 + 2
15    """
16  
17    def __init__(self, root: Optional[TreeNode]) -> None:
18        """
19        Initialize the FindElements object by recovering the tree values.
20      
21        Args:
22            root: The root of the contaminated binary tree
23        """
24        def recover_tree(node: Optional[TreeNode]) -> None:
25            """
26            Recursively recover tree values and store them in the set.
27          
28            Args:
29                node: Current tree node being processed
30            """
31            # Add current node's value to the set of recovered values
32            self.recovered_values.add(node.val)
33          
34            # Process left child if it exists
35            if node.left:
36                # Set left child value according to the recovery rule
37                node.left.val = node.val * 2 + 1
38                recover_tree(node.left)
39          
40            # Process right child if it exists
41            if node.right:
42                # Set right child value according to the recovery rule
43                node.right.val = node.val * 2 + 2
44                recover_tree(node.right)
45      
46        # Initialize root value to 0
47        root.val = 0
48      
49        # Initialize set to store all recovered values for O(1) lookup
50        self.recovered_values = set()
51      
52        # Start the recovery process from root
53        recover_tree(root)
54  
55    def find(self, target: int) -> bool:
56        """
57        Check if a target value exists in the recovered tree.
58      
59        Args:
60            target: The value to search for
61          
62        Returns:
63            True if the target exists in the recovered tree, False otherwise
64        """
65        return target in self.recovered_values
66
67
68# Your FindElements object will be instantiated and called as such:
69# obj = FindElements(root)
70# param_1 = obj.find(target)
71
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 *     int val;
5 *     TreeNode left;
6 *     TreeNode right;
7 *     TreeNode() {}
8 *     TreeNode(int val) { this.val = val; }
9 *     TreeNode(int val, TreeNode left, TreeNode right) {
10 *         this.val = val;
11 *         this.left = left;
12 *         this.right = right;
13 *     }
14 * }
15 */
16class FindElements {
17    // Set to store all recovered values in the tree
18    private Set<Integer> recoveredValues = new HashSet<>();
19
20    /**
21     * Constructor that recovers the contaminated binary tree.
22     * The root is set to 0, left children are 2*val+1, right children are 2*val+2.
23     * @param root The root of the contaminated binary tree
24     */
25    public FindElements(TreeNode root) {
26        // Set root value to 0 as per recovery rules
27        root.val = 0;
28        // Perform DFS to recover all node values and store them
29        recoverTree(root);
30    }
31
32    /**
33     * Checks if a target value exists in the recovered tree.
34     * @param target The value to search for
35     * @return true if the target exists in the recovered tree, false otherwise
36     */
37    public boolean find(int target) {
38        return recoveredValues.contains(target);
39    }
40
41    /**
42     * Recursive helper method to recover tree values using DFS traversal.
43     * For each node: left child = 2*parent+1, right child = 2*parent+2
44     * @param node The current node being processed
45     */
46    private void recoverTree(TreeNode node) {
47        // Add current node's recovered value to the set
48        recoveredValues.add(node.val);
49      
50        // Process left child if it exists
51        if (node.left != null) {
52            // Set left child value according to recovery formula
53            node.left.val = node.val * 2 + 1;
54            // Recursively process left subtree
55            recoverTree(node.left);
56        }
57      
58        // Process right child if it exists
59        if (node.right != null) {
60            // Set right child value according to recovery formula
61            node.right.val = node.val * 2 + 2;
62            // Recursively process right subtree
63            recoverTree(node.right);
64        }
65    }
66}
67
68/**
69 * Your FindElements object will be instantiated and called as such:
70 * FindElements obj = new FindElements(root);
71 * boolean param_1 = obj.find(target);
72 */
73
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 *     int val;
5 *     TreeNode *left;
6 *     TreeNode *right;
7 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class FindElements {
13public:
14    /**
15     * Constructor: Recovers the binary tree by assigning values based on the rule:
16     * - Root has value 0
17     * - Left child of node with value x has value 2*x + 1
18     * - Right child of node with value x has value 2*x + 2
19     * @param root The root of the contaminated binary tree
20     */
21    FindElements(TreeNode* root) {
22        // Set root value to 0 as per the recovery rule
23        root->val = 0;
24        // Perform DFS to recover all node values and store them
25        recoverTree(root);
26    }
27  
28    /**
29     * Checks if a target value exists in the recovered tree
30     * @param target The value to search for
31     * @return true if the target exists in the recovered tree, false otherwise
32     */
33    bool find(int target) {
34        return recovered_values_.count(target) > 0;
35    }
36  
37private:
38    // Set to store all recovered values for O(1) lookup
39    std::unordered_set<int> recovered_values_;
40  
41    /**
42     * DFS helper function to recover tree values and populate the set
43     * @param node Current node being processed
44     */
45    void recoverTree(TreeNode* node) {
46        // Store the current node's value in the set
47        recovered_values_.insert(node->val);
48      
49        // Process left child if it exists
50        if (node->left != nullptr) {
51            // Left child value = parent value * 2 + 1
52            node->left->val = node->val * 2 + 1;
53            recoverTree(node->left);
54        }
55      
56        // Process right child if it exists
57        if (node->right != nullptr) {
58            // Right child value = parent value * 2 + 2
59            node->right->val = node->val * 2 + 2;
60            recoverTree(node->right);
61        }
62    }
63};
64
65/**
66 * Your FindElements object will be instantiated and called as such:
67 * FindElements* obj = new FindElements(root);
68 * bool param_1 = obj->find(target);
69 */
70
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 *     val: number
5 *     left: TreeNode | null
6 *     right: TreeNode | null
7 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 *         this.val = (val===undefined ? 0 : val)
9 *         this.left = (left===undefined ? null : left)
10 *         this.right = (right===undefined ? null : right)
11 *     }
12 * }
13 */
14
15// Set to store all recovered values in the binary tree
16let recoveredValues: Set<number>;
17
18/**
19 * Initializes the FindElements data structure with a corrupted binary tree.
20 * Recovers the tree by setting root value to 0 and calculating child values.
21 * For any node with value x: left child = 2*x + 1, right child = 2*x + 2
22 * @param root - The root of the corrupted binary tree
23 */
24function FindElements(root: TreeNode | null): void {
25    // Initialize the set to store recovered values
26    recoveredValues = new Set<number>();
27  
28    // Set root value to 0 as per problem requirements
29    if (root) {
30        root.val = 0;
31    }
32  
33    // Perform depth-first search to recover all node values
34    performDFS(root, 0);
35}
36
37/**
38 * Performs depth-first search to traverse the tree and store recovered values
39 * @param node - Current node being processed
40 * @param nodeValue - The recovered value for the current node
41 */
42function performDFS(node: TreeNode | null, nodeValue: number = 0): void {
43    // Base case: if node is null, return
44    if (!node) {
45        return;
46    }
47  
48    // Add the current node's recovered value to the set
49    recoveredValues.add(nodeValue);
50  
51    // Recursively process left child with value 2*nodeValue + 1
52    performDFS(node.left, nodeValue * 2 + 1);
53  
54    // Recursively process right child with value 2*nodeValue + 2
55    performDFS(node.right, nodeValue * 2 + 2);
56}
57
58/**
59 * Checks if a target value exists in the recovered tree
60 * @param target - The value to search for
61 * @returns true if the target exists in the recovered tree, false otherwise
62 */
63function find(target: number): boolean {
64    // Check if the target value exists in the set of recovered values
65    return recoveredValues.has(target);
66}
67
68/**
69 * Your FindElements object will be instantiated and called as such:
70 * var obj = new FindElements(root)
71 * var param_1 = obj.find(target)
72 */
73

Time and Space Complexity

Time Complexity:

  • Initialization (__init__): O(n) where n is the number of nodes in the binary tree. The dfs function visits each node exactly once to recover its value and add it to the set.
  • Find operation (find): O(1) on average. Checking membership in a hash set is a constant-time operation.

Space Complexity:

  • Overall: O(n) where n is the number of nodes in the binary tree.
    • The set self.s stores all n node values, requiring O(n) space.
    • The recursive dfs function uses the call stack, which in the worst case (a skewed tree) can go up to O(n) depth, requiring O(n) space.
    • In a balanced tree, the recursion depth would be O(log n), but we consider the worst case.

The algorithm recovers the values of a contaminated binary tree where the root should be 0, left children should be 2 * parent_val + 1, and right children should be 2 * parent_val + 2. It stores all recovered values in a set for efficient lookup during the find operation.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Handle the Root Node Separately

A common mistake is diving straight into the DFS without first setting the root value to 0. Since all nodes start with value -1, the recursive formula alone won't work correctly.

Incorrect approach:

def __init__(self, root):
    self.recovered_values = set()
    self.recover_tree(root)  # Root still has value -1!

Correct approach:

def __init__(self, root):
    root.val = 0  # Must set root value first
    self.recovered_values = set()
    self.recover_tree(root)

2. Using Wrong Formula for Child Values

It's easy to confuse the formulas or accidentally swap them. Remember:

  • Left child: 2 * parent_val + 1
  • Right child: 2 * parent_val + 2

Common mistakes:

# Wrong: Using same formula for both children
node.left.val = node.val * 2 + 1
node.right.val = node.val * 2 + 1  # Should be +2!

# Wrong: Swapping the formulas
node.left.val = node.val * 2 + 2   # Should be +1
node.right.val = node.val * 2 + 1  # Should be +2

3. Not Checking for Node Existence Before Access

Attempting to access or modify child nodes without checking if they exist will cause AttributeError.

Incorrect approach:

def recover_tree(node):
    self.recovered_values.add(node.val)
    node.left.val = node.val * 2 + 1  # Error if node.left is None!
    recover_tree(node.left)

Correct approach:

def recover_tree(node):
    self.recovered_values.add(node.val)
    if node.left:
        node.left.val = node.val * 2 + 1
        recover_tree(node.left)

4. Using List Instead of Set for Storage

Using a list for storing recovered values would make the find() operation O(n) instead of O(1).

Inefficient approach:

self.recovered_values = []  # O(n) lookup time
# ...
def find(self, target):
    return target in self.recovered_values  # Linear search

Efficient approach:

self.recovered_values = set()  # O(1) lookup time

5. Modifying Values After Adding to Set

Adding the node value to the set before recovering it (when it's still -1) would store incorrect values.

Incorrect order:

def recover_tree(node):
    self.recovered_values.add(node.val)  # Adding -1 for non-root nodes!
    if node.left:
        node.left.val = node.val * 2 + 1
        recover_tree(node.left)

Correct approach for non-root nodes: Set the value first, then add to set during recursion, or ensure root is handled before starting DFS.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More