githubEdit

13. Maximum Non-Adjacent Nodes Sum

βœ… GFG solution to the Maximum Non-Adjacent Nodes Sum problem: find maximum sum of nodes in a binary tree where no two adjacent nodes are selected using dynamic programming on trees. πŸš€

The problem can be found at the following link: πŸ”— Question Linkarrow-up-right

🧩 Problem Description

Given the root of a binary tree with integer values, your task is to select a subset of nodes such that the sum of their values is maximized, with the condition that no two selected nodes are directly connected. That is, if a node is included in the subset, neither its parent nor its children can be included.

A node and its immediate parent or children form adjacent pairs in the tree structure. The goal is to maximize the sum while respecting this non-adjacency constraint.

πŸ“˜ Examples

Example 1

Input: root = [11, 1, 2]
Output: 11
Explanation: The maximum sum is obtained by selecting the node 11.

Example 2

Input: root = [1, 2, 3, 4, N, 5, 6]
Output: 16
Explanation: The maximum sum is obtained by selecting the nodes 1, 4, 5 and 6, 
which are not directly connected to each other. Their total sum is 16.

πŸ”’ Constraints

  • $1 \le \text{number of nodes} \le 10^4$

  • $1 \le \text{node.data} \le 10^5$

βœ… My Approach

The optimal approach uses Dynamic Programming on Trees with a recursive strategy to compute two states for each node:

Tree DP with State Tracking

  1. Define States:

    • For each node, maintain two values:

      • Include: Maximum sum when current node is included (children must be excluded).

      • Exclude: Maximum sum when current node is excluded (children can be either included or excluded).

  2. Recursive Computation:

    • Base case: If node is NULL, return (0, 0) representing both states as 0.

    • For each node, recursively compute states for left and right children.

  3. State Transition:

    • Include current node: Add node's value to the sum of excluded states of both children.

      • include = node->data + left_exclude + right_exclude

    • Exclude current node: Take maximum of both states for each child.

      • exclude = max(left_include, left_exclude) + max(right_include, right_exclude)

  4. Return Result:

    • Return pair of (include, exclude) for each node.

    • Final answer is the maximum of both states at root.

  5. Post-Order Traversal:

    • Process children before parent to ensure bottom-up computation.

    • Each node uses computed results from its subtrees.

πŸ“ Time and Auxiliary Space Complexity

  • Expected Time Complexity: O(n), where n is the number of nodes in the binary tree. We visit each node exactly once during the post-order traversal, performing constant time operations at each node.

  • Expected Auxiliary Space Complexity: O(h), where h is the height of the binary tree. This space is used by the recursion call stack. In the worst case of a skewed tree, h can be O(n), but for a balanced tree, it is O(log n).

πŸ§‘β€πŸ’» Code (C++)

chevron-right⚑ View Alternative Approaches with Code and Analysishashtag

πŸ“Š 2️⃣ Dynamic Programming with Memoization

πŸ’‘ Algorithm Steps:

  1. Use a map to cache computed results for each node.

  2. For each node, compute two states: including and excluding current node.

  3. Return cached result if node is already processed.

  4. Recursively solve for left and right subtrees with memoization.

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(n) - Visit each node once

  • Auxiliary Space: πŸ’Ύ O(n) - HashMap storage for memoization

βœ… Why This Approach?

  • Avoids recomputation with caching

  • Good for repeated queries on same tree

  • Clear separation of state management

πŸ“Š 3️⃣ Bottom-Up Array Return

πŸ’‘ Algorithm Steps:

  1. Post-order traversal returning array for each subtree.

  2. Index 0 stores maximum sum including root.

  3. Index 1 stores maximum sum excluding root.

  4. Combine left and right results at each node.

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(n) - Single traversal of tree

  • Auxiliary Space: πŸ’Ύ O(h) - Recursion stack height

βœ… Why This Approach?

  • Clean separation of helper and main function

  • Easy to understand state representation

  • Standard DP on trees pattern

πŸ“Š 4️⃣ Iterative Post-Order with Stack

πŸ’‘ Algorithm Steps:

  1. Use stack to simulate post-order traversal iteratively.

  2. Store state for each node in separate map.

  3. Process children before parent to maintain bottom-up order.

  4. Compute include and exclude states from children states.

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(n) - Each node visited once

  • Auxiliary Space: πŸ’Ύ O(n) - Stack and map storage

βœ… Why This Approach?

  • Avoids recursion stack overflow for deep trees

  • More control over traversal order

  • Useful when recursion depth is limited

πŸ†š πŸ” Comparison of Approaches

πŸš€ Approach

⏱️ Time Complexity

πŸ’Ύ Space Complexity

βœ… Pros

⚠️ Cons

🎯 Pair Return

🟒 O(n)

🟒 O(h)

πŸš€ Clean and concise

πŸ”§ Modern C++ features required

πŸ’Ύ Memoization

🟒 O(n)

🟑 O(n)

πŸ”„ Reusable cache

πŸ’Ύ Extra space for map

πŸ“Š Array Return

🟒 O(n)

🟒 O(h)

πŸ“– Clear state representation

πŸ”§ Extra vector allocations

πŸ”„ Iterative

🟒 O(n)

🟑 O(n)

πŸ›‘οΈ No recursion limit

🐌 Complex implementation

πŸ† Best Choice Recommendation

🎯 Scenario

πŸŽ–οΈ Recommended Approach

πŸ”₯ Performance Rating

πŸ… Modern C++ competitive coding

πŸ₯‡ Pair Return

β˜…β˜…β˜…β˜…β˜…

πŸ“– Learning DP on trees

πŸ₯ˆ Array Return

β˜…β˜…β˜…β˜…β˜†

πŸ”§ Deep tree scenarios

πŸ₯‰ Iterative

β˜…β˜…β˜…β˜…β˜†

🎯 Multiple queries on same tree

πŸ… Memoization

β˜…β˜…β˜…β˜…β˜†

β˜• Code (Java)

🐍 Code (Python)

🧠 Contribution and Support

For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: πŸ“¬ Any Questions?arrow-up-right. Let's make this learning journey more collaborative!

⭐ If you find this helpful, please give this repository a star! ⭐


πŸ“Visitor Count

Visitor counter

Last updated