|
| 1 | +<h1 align='center'>Tree - Boundary - Traversal</h1> |
| 2 | + |
| 3 | +## Problem Statement |
| 4 | + |
| 5 | +**Problem URL :** [Tree Boundary Traversal](https://www.geeksforgeeks.org/problems/boundary-traversal-of-binary-tree/1) |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | +## Problem Explanation |
| 13 | + |
| 14 | +The goal is to traverse the boundary of a binary tree. The boundary of a tree consists of: |
| 15 | +1. The **left boundary** (excluding the leaves). |
| 16 | +2. The **leaf nodes** (from left to right). |
| 17 | +3. The **right boundary** (excluding the leaves and in reverse order). |
| 18 | + |
| 19 | +### Example |
| 20 | +Given the binary tree: |
| 21 | + |
| 22 | +``` |
| 23 | + 1 |
| 24 | + / \ |
| 25 | + 2 3 |
| 26 | + / \ \ |
| 27 | + 4 5 6 |
| 28 | + / \ |
| 29 | + 7 8 |
| 30 | +``` |
| 31 | + |
| 32 | +**Boundary Traversal Output**: |
| 33 | +- **Left Boundary**: `1, 2` (from top to bottom, excluding leaves) |
| 34 | +- **Leaf Nodes**: `4, 7, 8, 6` (in left to right order) |
| 35 | +- **Right Boundary**: `3` (from bottom to top, excluding leaves) |
| 36 | + |
| 37 | +**Final Result**: `[1, 2, 4, 7, 8, 6, 3]` |
| 38 | + |
| 39 | +## Step 2: Approach |
| 40 | + |
| 41 | +### Approach Explanation |
| 42 | +1. **Identify the Parts of the Boundary**: |
| 43 | + - **Left Boundary**: Traverse down the leftmost path, skipping leaf nodes. |
| 44 | + - **Leaf Nodes**: Traverse the entire tree and collect leaf nodes. |
| 45 | + - **Right Boundary**: Traverse down the rightmost path in reverse, skipping leaf nodes. |
| 46 | + |
| 47 | +2. **Implementation**: |
| 48 | + - Create three helper functions to handle left boundary traversal, leaf node collection, and right boundary traversal. |
| 49 | + - Use a vector to store the result and return it after processing all parts. |
| 50 | + |
| 51 | +### Beginner-Friendly Steps |
| 52 | +1. Start with the root node. If it's `NULL`, return an empty vector. |
| 53 | +2. Add the root's value to the result vector. |
| 54 | +3. Implement a recursive function to traverse the left boundary and skip leaf nodes. |
| 55 | +4. Implement another function to collect all leaf nodes by recursively checking left and right children. |
| 56 | +5. Implement the right boundary function to traverse the rightmost path, skipping leaf nodes, and store them in reverse order. |
| 57 | +6. Combine all the collected nodes in the final result. |
| 58 | + |
| 59 | +```cpp |
| 60 | +class Solution { |
| 61 | +public: |
| 62 | + void traverseLeft(Node* root, vector<int> &ans){ |
| 63 | + if(root == NULL || (root -> left == NULL && root -> right == NULL)) return; |
| 64 | + |
| 65 | + ans.push_back(root -> data); |
| 66 | + |
| 67 | + if(root -> left) traverseLeft(root -> left, ans); |
| 68 | + else traverseLeft(root -> right, ans); |
| 69 | + } |
| 70 | + |
| 71 | + void traverseLeaf(Node* root, vector<int> &ans){ |
| 72 | + if(root == NULL) return; |
| 73 | + |
| 74 | + if (root->left == NULL && root->right == NULL) { |
| 75 | + ans.push_back(root->data); |
| 76 | + return; |
| 77 | + } |
| 78 | + |
| 79 | + traverseLeaf(root -> left, ans); |
| 80 | + traverseLeaf(root -> right, ans); |
| 81 | + |
| 82 | + } |
| 83 | + |
| 84 | + void traverseRight(Node* root, vector<int> &ans){ |
| 85 | + if(root == NULL || (root -> left == NULL && root -> right == NULL)) return; |
| 86 | + |
| 87 | + |
| 88 | + if(root -> right) traverseRight(root -> right, ans); |
| 89 | + else traverseRight(root -> left, ans); |
| 90 | + |
| 91 | + ans.push_back(root -> data); |
| 92 | + } |
| 93 | + |
| 94 | + |
| 95 | + vector <int> boundary(Node *root) |
| 96 | + { |
| 97 | + vector<int> ans; |
| 98 | + if(root == NULL) return ans; |
| 99 | + |
| 100 | + ans.push_back(root -> data); |
| 101 | + |
| 102 | + traverseLeft(root -> left, ans); |
| 103 | + |
| 104 | + traverseLeaf(root -> left, ans); |
| 105 | + traverseLeaf(root -> right, ans); |
| 106 | + |
| 107 | + traverseRight(root -> right, ans); |
| 108 | + |
| 109 | + return ans; |
| 110 | + } |
| 111 | +}; |
| 112 | +``` |
| 113 | + |
| 114 | +## Problem Solution Explanation |
| 115 | +Let's break down the code line by line: |
| 116 | + |
| 117 | +```cpp |
| 118 | +class Solution { |
| 119 | +public: |
| 120 | + void traverseLeft(Node* root, vector<int> &ans){ |
| 121 | +``` |
| 122 | +- **Line 1**: Defines the class `Solution` and declares the method `traverseLeft` to traverse the left boundary of the tree. |
| 123 | +- **Line 2**: Takes a pointer to the root node of the subtree and a reference to a vector `ans` to store results. |
| 124 | +
|
| 125 | +```cpp |
| 126 | + if(root == NULL || (root -> left == NULL && root -> right == NULL)) return; |
| 127 | +``` |
| 128 | +- **Line 3**: Checks if the current node is `NULL` or if it's a leaf node (both children are `NULL`). If true, the function returns since there's nothing to add. |
| 129 | + |
| 130 | +### Example: |
| 131 | +- If `root` points to a leaf node like `4`, the function returns without adding anything to `ans`. |
| 132 | + |
| 133 | +```cpp |
| 134 | + ans.push_back(root -> data); |
| 135 | +``` |
| 136 | +- **Line 4**: Adds the current node's data to the `ans` vector, as it’s part of the left boundary. |
| 137 | + |
| 138 | +### Example: |
| 139 | +- For node `2`, `ans` becomes `[2]`. |
| 140 | + |
| 141 | +```cpp |
| 142 | + if(root -> left) traverseLeft(root -> left, ans); |
| 143 | + else traverseLeft(root -> right, ans); |
| 144 | +``` |
| 145 | +- **Lines 5-6**: If there’s a left child, recursively call `traverseLeft` on the left child. If there’s no left child, call it on the right child. |
| 146 | + |
| 147 | +### Example: |
| 148 | +- From `2`, it goes to `4` (the left child). If it were `3`, it would have traversed the right child instead. |
| 149 | + |
| 150 | +```cpp |
| 151 | + void traverseLeaf(Node* root, vector<int> &ans){ |
| 152 | +``` |
| 153 | +- **Line 8**: Declares the `traverseLeaf` function to collect leaf nodes. |
| 154 | +
|
| 155 | +```cpp |
| 156 | + if(root == NULL) return; |
| 157 | +``` |
| 158 | +- **Line 9**: Checks if the current node is `NULL`. If true, returns. |
| 159 | + |
| 160 | +```cpp |
| 161 | + if (root->left == NULL && root->right == NULL) { |
| 162 | + ans.push_back(root->data); |
| 163 | + return; |
| 164 | + } |
| 165 | +``` |
| 166 | +- **Lines 10-12**: Checks if the current node is a leaf. If so, adds it to `ans` and returns. |
| 167 | +
|
| 168 | +### Example: |
| 169 | +- For node `4`, it gets added to `ans`: `ans = [2, 4]`. |
| 170 | +
|
| 171 | +```cpp |
| 172 | + traverseLeaf(root -> left, ans); |
| 173 | + traverseLeaf(root -> right, ans); |
| 174 | +``` |
| 175 | +- **Lines 13-14**: Recursively calls `traverseLeaf` on the left and right children. |
| 176 | + |
| 177 | +### Example: |
| 178 | +- From `2`, it will recursively check `4`, `5`, and subsequently `7` and `8`. |
| 179 | + |
| 180 | +```cpp |
| 181 | + void traverseRight(Node* root, vector<int> &ans){ |
| 182 | +``` |
| 183 | +- **Line 16**: Declares the `traverseRight` function for traversing the right boundary. |
| 184 | +
|
| 185 | +```cpp |
| 186 | + if(root == NULL || (root -> left == NULL && root -> right == NULL)) return; |
| 187 | +``` |
| 188 | +- **Line 17**: Checks if the current node is `NULL` or a leaf node. If so, it returns. |
| 189 | + |
| 190 | +```cpp |
| 191 | + if(root -> right) traverseRight(root -> right, ans); |
| 192 | + else traverseRight(root -> left, ans); |
| 193 | +``` |
| 194 | +- **Lines 18-19**: First, traverses the right child. If it doesn’t exist, it goes to the left child. |
| 195 | + |
| 196 | +### Example: |
| 197 | +- For node `3`, it goes right to `6`, adding `3` later after the recursive calls return. |
| 198 | + |
| 199 | +```cpp |
| 200 | + ans.push_back(root -> data); |
| 201 | +``` |
| 202 | +- **Line 20**: Adds the current node’s data to the `ans` vector after the recursive calls have returned (this ensures it adds in reverse order). |
| 203 | + |
| 204 | +### Example: |
| 205 | +- After visiting `6`, it adds `3` to `ans`: `ans = [2, 4, 7, 8, 6, 3]` by the end. |
| 206 | + |
| 207 | +```cpp |
| 208 | + vector <int> boundary(Node *root) |
| 209 | + { |
| 210 | + vector<int> ans; // Result vector to hold the boundary values. |
| 211 | + if(root == NULL) return ans; // If root is NULL, return empty result. |
| 212 | +``` |
| 213 | +- **Line 22-24**: Declares the main function `boundary`, initializes an empty vector `ans`, and checks if `root` is `NULL`. If true, it returns `ans`. |
| 214 | +
|
| 215 | +```cpp |
| 216 | + ans.push_back(root -> data); // Add the root's data to the result. |
| 217 | +``` |
| 218 | +- **Line 25**: Adds the root's data to `ans` as it is part of the boundary. |
| 219 | + |
| 220 | +### Example: |
| 221 | +- If the root is `1`, `ans` becomes: `ans = [1]`. |
| 222 | + |
| 223 | +```cpp |
| 224 | + traverseLeft(root -> left, ans); |
| 225 | +``` |
| 226 | +- **Line 26**: Calls `traverseLeft` on the left child of the root to collect the left boundary. |
| 227 | +
|
| 228 | +```cpp |
| 229 | + traverseLeaf(root -> left, ans); |
| 230 | + traverseLeaf(root -> right, ans); |
| 231 | +``` |
| 232 | +- **Lines 27-28**: Calls `traverseLeaf` on both left and right subtrees to collect all leaf nodes. |
| 233 | + |
| 234 | +```cpp |
| 235 | + traverseRight(root -> right, ans); |
| 236 | +``` |
| 237 | +- **Line 29**: Calls `traverseRight` on the right child of the root to collect the right boundary. |
| 238 | +
|
| 239 | +```cpp |
| 240 | + return ans; // Return the final boundary traversal. |
| 241 | +``` |
| 242 | +- **Line 30**: Returns the completed `ans` vector containing the boundary values. |
| 243 | + |
| 244 | +## Step 4: Time and Space Complexity |
| 245 | + |
| 246 | +### Time Complexity |
| 247 | +- **Traversal Functions**: |
| 248 | + - Each of the three traversal functions (`traverseLeft`, `traverseLeaf`, `traverseRight`) will visit every node in the tree once. |
| 249 | + - Thus, the overall time complexity is **O(n)**, where `n` is the number of nodes in the tree. |
| 250 | + |
| 251 | +### Space Complexity |
| 252 | +- **Space Complexity**: |
| 253 | + - The space complexity is primarily due to the recursion stack used in the traversal functions. In the worst case (e.g., a skewed tree), the maximum depth of recursion can be `n`. |
| 254 | + - However, since we're using a vector to store the results, the overall space complexity is **O(h)** for the recursion stack and **O(n)** for storing the boundary nodes in the result vector, where `h` is the height of the tree. |
| 255 | + |
| 256 | +In summary, the time complexity is **O(n)**, and the space complexity is **O(n)** due to the storage of boundary nodes and potential recursion depth. |
| 257 | + |
| 258 | +This comprehensive explanation should help you understand the problem, the approach taken, and how the provided code works in detail! |
0 commit comments