|
| 1 | +<h1 align='center'>Maximum - Sum of - Non-adjacent - Nodes</h1> |
| 2 | + |
| 3 | +## Problem Statement |
| 4 | + |
| 5 | +**Problem URL :** [Maximum Sum of Non-adjacent Nodes](https://www.geeksforgeeks.org/problems/maximum-sum-of-non-adjacent-nodes/1) |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +## Problem Explanation |
| 11 | + |
| 12 | +### Problem Description |
| 13 | +The task is to find the maximum sum of non-adjacent nodes in a binary tree. Nodes are considered non-adjacent if they are not direct children of each other. |
| 14 | + |
| 15 | +### Constraints |
| 16 | +1. Each node in the tree contains an integer value. |
| 17 | +2. The tree can have both positive and negative values. |
| 18 | +3. The structure of the tree can vary; it may be balanced or skewed. |
| 19 | +4. The maximum number of nodes is not specified but is usually within reasonable limits for typical binary trees. |
| 20 | + |
| 21 | +### Real-World Analogy |
| 22 | +Imagine a situation where each node represents a house, and the value of the node is the amount of money in each house. You can only rob houses that are not directly next to each other (i.e., no two adjacent houses can be robbed). The goal is to maximize the total amount of money stolen without alerting the police (which happens if you rob adjacent houses). |
| 23 | + |
| 24 | +### Edge Cases |
| 25 | +1. An empty tree should return 0. |
| 26 | +2. A tree with only one node should return the value of that node. |
| 27 | +3. A tree with all negative values should still return the maximum (least negative) node value. |
| 28 | + |
| 29 | +## Step 2: Approach |
| 30 | + |
| 31 | +### High-Level Overview |
| 32 | +The problem can be solved using a recursive approach with dynamic programming principles, where we compute two values at each node: |
| 33 | +1. The maximum sum including the node itself. |
| 34 | +2. The maximum sum excluding the node. |
| 35 | + |
| 36 | +The maximum sum at any node is then determined by taking the maximum of these two values. |
| 37 | + |
| 38 | +### Step-by-Step Breakdown |
| 39 | +1. **Recursive Function**: Create a recursive function that returns a pair of integers for each node: |
| 40 | + - The first integer represents the maximum sum when including that node. |
| 41 | + - The second integer represents the maximum sum when excluding that node. |
| 42 | +2. **Base Case**: If the current node is NULL, return (0, 0) since there are no nodes to consider. |
| 43 | +3. **Recursive Case**: For each node: |
| 44 | + - Calculate the results from the left and right children recursively. |
| 45 | + - The result for the current node when included is its value plus the sums from the children when excluded. |
| 46 | + - The result for the current node when excluded is the maximum sums from both children, regardless of whether they are included or excluded. |
| 47 | +4. **Combine Results**: Return the calculated pair for the current node. |
| 48 | + |
| 49 | +### Pseudocode |
| 50 | +```plaintext |
| 51 | +function solve(node): |
| 52 | + if node is NULL: |
| 53 | + return (0, 0) |
| 54 | +
|
| 55 | + left = solve(node.left) |
| 56 | + right = solve(node.right) |
| 57 | +
|
| 58 | + include_current = node.value + left.second + right.second |
| 59 | + exclude_current = max(left.first, left.second) + max(right.first, right.second) |
| 60 | +
|
| 61 | + return (include_current, exclude_current) |
| 62 | +
|
| 63 | +function getMaxSum(root): |
| 64 | + ans = solve(root) |
| 65 | + return max(ans.first, ans.second) |
| 66 | +``` |
| 67 | + |
| 68 | +## Problem Solution |
| 69 | +```cpp |
| 70 | +class Solution{ |
| 71 | + public: |
| 72 | + pair<int, int> solve(Node* root){ |
| 73 | + if(root == NULL) { |
| 74 | + pair<int, int> p = make_pair(0, 0); |
| 75 | + return p; |
| 76 | + }; |
| 77 | + |
| 78 | + |
| 79 | + pair<int, int> left = solve(root -> left); |
| 80 | + pair<int, int> right = solve(root -> right); |
| 81 | + |
| 82 | + pair<int, int> res; |
| 83 | + res.first = root -> data + left.second + right.second; |
| 84 | + res.second = max(left.first, left.second) + max(right.first, right.second); |
| 85 | + |
| 86 | + |
| 87 | + return res; |
| 88 | + |
| 89 | + } |
| 90 | + //Function to return the maximum sum of non-adjacent nodes. |
| 91 | + int getMaxSum(Node *root) |
| 92 | + { |
| 93 | + pair<int, int> ans = solve(root); |
| 94 | + return max(ans.first, ans.second); |
| 95 | + |
| 96 | + } |
| 97 | +}; |
| 98 | +``` |
| 99 | + |
| 100 | +## Problem Solution Explanation |
| 101 | + |
| 102 | +Let's go through the code line by line: |
| 103 | + |
| 104 | +#### Class Definition |
| 105 | +```cpp |
| 106 | +class Solution { |
| 107 | +public: |
| 108 | +``` |
| 109 | +- This line defines a class named `Solution`. All the methods inside this class will be related to solving the problem. |
| 110 | +
|
| 111 | +#### Recursive Function: `solve` |
| 112 | +```cpp |
| 113 | +pair<int, int> solve(Node* root) { |
| 114 | +``` |
| 115 | +- This function takes a pointer to the root of a binary tree (`Node* root`) and returns a pair of integers (`pair<int, int>`). The pair consists of: |
| 116 | + - `first`: The maximum sum of non-adjacent nodes including the current node. |
| 117 | + - `second`: The maximum sum of non-adjacent nodes excluding the current node. |
| 118 | + |
| 119 | +##### Base Case |
| 120 | +```cpp |
| 121 | +if (root == NULL) { |
| 122 | + pair<int, int> p = make_pair(0, 0); |
| 123 | + return p; |
| 124 | +} |
| 125 | +``` |
| 126 | +- **Explanation**: If the current node (`root`) is `NULL`, it means we've reached the end of a branch in the tree. |
| 127 | +- We return a pair `(0, 0)`, indicating that there are no nodes to consider in this path. |
| 128 | +- **Example**: If we call `solve` on a leaf node's child (which is `NULL`), it returns `(0, 0)`. |
| 129 | + |
| 130 | +##### Recursive Calls |
| 131 | +```cpp |
| 132 | +pair<int, int> left = solve(root->left); |
| 133 | +pair<int, int> right = solve(root->right); |
| 134 | +``` |
| 135 | +- **Explanation**: We make two recursive calls to solve the left and right subtrees of the current node: |
| 136 | + - `left` will hold the result from the left child. |
| 137 | + - `right` will hold the result from the right child. |
| 138 | +- Each recursive call will return a pair containing the sums for its respective subtree. |
| 139 | + |
| 140 | +**Example**: |
| 141 | +For a tree like: |
| 142 | +``` |
| 143 | + 10 |
| 144 | + / \ |
| 145 | + 1 2 |
| 146 | + / \ \ |
| 147 | + 3 4 5 |
| 148 | +``` |
| 149 | +- Calling `solve(10)` will call `solve(1)` and `solve(2)`. |
| 150 | + |
| 151 | +##### Result Calculation |
| 152 | +```cpp |
| 153 | +pair<int, int> res; |
| 154 | +``` |
| 155 | +- **Explanation**: We create a new pair `res` to store the results for the current node. |
| 156 | + |
| 157 | +```cpp |
| 158 | +res.first = root->data + left.second + right.second; |
| 159 | +``` |
| 160 | +- **Explanation**: This calculates the maximum sum when including the current node (`root`): |
| 161 | + - `root->data`: Value of the current node. |
| 162 | + - `left.second`: The maximum sum of non-adjacent nodes from the left child when it's excluded. |
| 163 | + - `right.second`: The maximum sum of non-adjacent nodes from the right child when it's excluded. |
| 164 | +- The logic here is that if we include the current node, we cannot include its immediate children, hence we add the `second` values from both children. |
| 165 | + |
| 166 | +**Example**: |
| 167 | +If `root->data` is `10`, `left.second` is `4` (sum excluding node `1`), and `right.second` is `5` (sum excluding node `2`), then: |
| 168 | +- `res.first = 10 + 4 + 5 = 19`. |
| 169 | + |
| 170 | +```cpp |
| 171 | +res.second = max(left.first, left.second) + max(right.first, right.second); |
| 172 | +``` |
| 173 | +- **Explanation**: This calculates the maximum sum when excluding the current node: |
| 174 | + - `max(left.first, left.second)`: Choose the maximum sum from the left child, whether we include it or not. |
| 175 | + - `max(right.first, right.second)`: Choose the maximum sum from the right child, whether we include it or not. |
| 176 | +- The idea here is that if we exclude the current node, we can freely choose to include or exclude its children based on which gives a higher sum. |
| 177 | + |
| 178 | +**Example**: |
| 179 | +Continuing from our previous example, if: |
| 180 | +- `left.first = 7` (including node `1`), |
| 181 | +- `left.second = 4` (excluding node `1`), |
| 182 | +- `right.first = 5` (including node `2`), |
| 183 | +- `right.second = 5` (excluding node `2`), |
| 184 | +Then: |
| 185 | +- `res.second = max(7, 4) + max(5, 5) = 7 + 5 = 12`. |
| 186 | + |
| 187 | +##### Return Result |
| 188 | +```cpp |
| 189 | +return res; |
| 190 | +``` |
| 191 | +- **Explanation**: This returns the computed pair `res` for the current subtree. |
| 192 | +- The function will eventually return results all the way back up to the root. |
| 193 | + |
| 194 | +#### Main Function: `getMaxSum` |
| 195 | +```cpp |
| 196 | +int getMaxSum(Node *root) { |
| 197 | +``` |
| 198 | +- This function is called to get the maximum sum of non-adjacent nodes in the entire binary tree. |
| 199 | +
|
| 200 | +```cpp |
| 201 | +pair<int, int> ans = solve(root); |
| 202 | +``` |
| 203 | +- **Explanation**: Calls the `solve` function with the root node and stores the result in `ans`. |
| 204 | + |
| 205 | +```cpp |
| 206 | +return max(ans.first, ans.second); |
| 207 | +``` |
| 208 | +- **Explanation**: Returns the maximum of the two sums computed from `solve(root)`. |
| 209 | +- This gives the overall maximum sum of non-adjacent nodes in the tree. |
| 210 | + |
| 211 | +### Example Execution Flow |
| 212 | +Let's trace the execution for the tree: |
| 213 | +``` |
| 214 | + 10 |
| 215 | + / \ |
| 216 | + 1 2 |
| 217 | + / \ \ |
| 218 | + 3 4 5 |
| 219 | +``` |
| 220 | + |
| 221 | +1. **Start at the root (10)**: |
| 222 | + - Call `solve(10)`. |
| 223 | +2. **Move to the left child (1)**: |
| 224 | + - Call `solve(1)`. |
| 225 | +3. **Move to the left child (3)**: |
| 226 | + - Call `solve(3)`. |
| 227 | + - Both children of 3 are NULL, so it returns `(3, 0)`. |
| 228 | +4. **Return to (1)** and process the right child (4): |
| 229 | + - Call `solve(4)`. |
| 230 | + - Both children of 4 are NULL, so it returns `(4, 0)`. |
| 231 | +5. **Now for node (1)**: |
| 232 | + - `res.first = 1 + 0 + 0 = 1` (including 1) |
| 233 | + - `res.second = max(3, 0) + max(4, 0) = 3 + 4 = 7` (excluding 1) |
| 234 | + - Returns `(1, 7)` for node (1). |
| 235 | +6. **Return to the root (10)** and process the right child (2): |
| 236 | + - Call `solve(2)`. |
| 237 | +7. **Move to the right child (5)**: |
| 238 | + - Call `solve(5)`. |
| 239 | + - Both children of 5 are NULL, so it returns `(5, 0)`. |
| 240 | +8. **Now for node (2)**: |
| 241 | + - Left child is NULL, so `left = (0, 0)`. |
| 242 | + - `res.first = 2 + 0 + 0 = 2`. |
| 243 | + - `res.second = max(0, 0) + max(5, 0) = 0 + 5 = 5`. |
| 244 | + - Returns `(2, 5)` for node (2). |
| 245 | +9. **Finally for the root (10)**: |
| 246 | + - `res.first = 10 + 7 + 5 = 22`. |
| 247 | + - `res.second = max(1, 7) + max(2, 5) = 7 + 5 = 12`. |
| 248 | + - Returns `(22, 12)`. |
| 249 | + |
| 250 | +The final answer from `getMaxSum(root)` is `max(22, 12)`, which is `22`. |
| 251 | + |
| 252 | +Let's consider the following binary tree: |
| 253 | +``` |
| 254 | + 10 |
| 255 | + / \ |
| 256 | + 1 2 |
| 257 | + / \ \ |
| 258 | + 3 4 5 |
| 259 | +``` |
| 260 | + |
| 261 | +### Example 1 |
| 262 | +**Input**: The tree above. |
| 263 | + |
| 264 | +- **Expected Output**: The maximum sum of non-adjacent nodes is `10 + 4 + 5 = 19`. |
| 265 | + |
| 266 | +### Example 2 |
| 267 | +**Input**: An empty tree. |
| 268 | + |
| 269 | +- **Expected Output**: `0`, since there are no nodes. |
| 270 | + |
| 271 | +### Example 3 |
| 272 | +**Input**: A tree with all negative values. |
| 273 | +``` |
| 274 | + -1 |
| 275 | + / \ |
| 276 | + -2 -3 |
| 277 | + / \ \ |
| 278 | + -4 -5 -6 |
| 279 | +``` |
| 280 | + |
| 281 | +- **Expected Output**: The maximum (least negative) node value is `-1`. |
| 282 | + |
| 283 | +### Example 4 |
| 284 | +**Input**: A single-node tree. |
| 285 | +``` |
| 286 | + 7 |
| 287 | +``` |
| 288 | + |
| 289 | +- **Expected Output**: `7`, as it's the only node. |
| 290 | + |
| 291 | +### Explanation of Outputs |
| 292 | +- The maximum sum is calculated by selecting nodes that are non-adjacent while maximizing their total value. In each example, we carefully select which nodes to include based on their position in the tree. |
| 293 | + |
| 294 | +## Step 5: Time and Space Complexity |
| 295 | + |
| 296 | +### Time Complexity |
| 297 | +- **Recursive Calls**: Each node in the binary tree is visited once. Therefore, the time complexity is **O(N)**, where N is the number of nodes in the tree. |
| 298 | + |
| 299 | +### Space Complexity |
| 300 | +- **Recursive Stack Space**: The space complexity due to the recursion stack can go up to **O(H)**, where H is the height of the tree. In the worst case (for example, a skewed tree), this can be **O(N)**. For balanced trees, it is **O(log N)**. |
| 301 | + |
| 302 | +### Summary |
| 303 | +- The solution is efficient for typical binary trees, visiting each node once and storing results in pairs. It effectively handles various tree structures while maximizing the sum of non-adjacent nodes. |
| 304 | + |
| 305 | +With this detailed breakdown, you should have a clear understanding of the problem, the approach taken, the code implementation, and the complexities involved. If you have any further questions or need clarifications, feel free to ask! |
0 commit comments