Problem Statement
Given the root of a binary tree. Return all the root-to-leaf paths in the binary tree.
A leaf node of a binary tree is the node which does not have a left and right child.
Examples
Example 1:
Input:
1
/ \
2 3
/ \
4 5
Output: [
[1, 2, 4],
[1, 2, 5],
[1, 3]
]
Explanation:
The root-to-leaf paths are:
1 → 2 → 4
1 → 2 → 5
1 → 3
Example 2:
Input:
1
/ \
2 3
\
5
Output: [
[1, 2, 5],
[1, 3]
]
Explanation:
The root-to-leaf paths are:
1 → 2 → 5
1 → 3
Example 3:
Input:
1
\
2
\
3
Output: [
[1, 2, 3]
]
Explanation:
The root-to-leaf path is 1 → 2 → 3. Since there's only one leaf, there is only one path.
Different Approaches
1️⃣ Depth First Search
Intuition:
To find all root-to-leaf paths in a binary tree:
- A root-to-leaf path starts at the root and ends at a leaf node (a node with no children).
- This can be achieved through DFS traversal while maintaining a list of nodes that constitute the current path.
- Upon reaching a leaf node, the current path is stored in the result list.
To implement this:
- Use recursion to traverse the tree.
- Pass the current path in each recursive call.
- When a leaf node is reached, add the current path to the result list.
- Backtrack after visiting a node to ensure the path is correct for sibling nodes.
Approach:
- Base Case:
- If the root is
nullptr
, there are no paths, so return an empty list.
- If the root is
- Recursive Case:
- Add the current node to the path.
- If the node is a leaf node, store the current path in the result.
- Otherwise, recursively call the function for the left and right children.
- Backtracking:
- After returning from recursion, remove the current node from the path to ensure it doesn't affect other paths.
Code:
#include <iostream>
#include <vector>
using namespace std;
// Definition for a binary tree node
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
void dfs(TreeNode* node, vector<int>& path, vector<vector<int>>& result) {
if (!node) return;
// Add current node to the path
path.push_back(node->val);
// If it's a leaf node, save the current path
if (!node->left && !node->right) {
result.push_back(path);
} else {
// Recur for left and right subtrees
dfs(node->left, path, result);
dfs(node->right, path, result);
}
// Backtrack: remove the current node from the path
path.pop_back();
}
vector<vector<int>> allRootToLeafPaths(TreeNode* root) {
vector<vector<int>> result;
vector<int> path;
dfs(root, path, result);
return result;
}
};
// Helper function to build a sample tree
TreeNode* buildTree() {
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
return root;
}
// Main function
int main() {
Solution sol;
TreeNode* root = buildTree();
vector<vector<int>> paths = sol.allRootToLeafPaths(root);
for (const auto& path : paths) {
for (int val : path) {
cout << val << " ";
}
cout << endl;
}
return 0;
}
Complexity Analysis:
- Time Complexity:
O(N)
whereN
is the number of nodes in the binary tree. Each node of the binary tree is visited exactly once during the traversal. - Space Complexity:
O(N)
whereN
is the number of nodes in the binary tree. This is because the stack can potentially hold all nodes in the tree when dealing with a skewed tree (all nodes have only one child), consuming space proportional to the number of nodes.