7. Root to Leaf Paths

The problem can be found at the following link: ๐Ÿ”— Question Link

๐Ÿงฉ Problem Description

Given a binary tree, find all the possible paths from the root node to all the leaf nodes. A leaf node is a node that does not have any children. The paths should be returned in such a way that the paths from the left subtree of any node are listed first, followed by paths from the right subtree.

Examples

Example 1:

Input:

root = [1, 2, 3, 4, 5, null, null]

Output:

[[1, 2, 4], [1, 2, 5], [1, 3]]

Explanation:

All the possible paths from root node to leaf nodes are:

  • 1 โ†’ 2 โ†’ 4

  • 1 โ†’ 2 โ†’ 5

  • 1 โ†’ 3

Example 2:

Input:

root = [1, 2, 3]

Output:

[[1, 2], [1, 3]]

Explanation:

All the possible paths from root node to leaf nodes are:

  • 1 โ†’ 2

  • 1 โ†’ 3

Example 3:

Input:

root = [10, 20, 30, 40, 60, null, null]

Output:

[[10, 20, 40], [10, 20, 60], [10, 30]]

Explanation:

All the possible paths from root node to leaf nodes are:

  • 10 โ†’ 20 โ†’ 40

  • 10 โ†’ 20 โ†’ 60

  • 10 โ†’ 30

๐Ÿ”’ Constraints

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

  • $1 \leq \text{node->data} \leq 10^4$

โœ… My Approach

Depth-First Search (DFS)

We can solve this problem using a Depth-First Search (DFS) approach. Starting from the root node, we traverse the tree recursively, maintaining the current path. If we reach a leaf node (a node with no left or right child), we add the current path to the result list.

Algorithm Steps:

  1. Initialize an empty list res to store the paths.

  2. Define a recursive DFS function that:

    • Adds the current node's data to the current path.

    • If it's a leaf node (no left or right children), add the path to res.

    • Recursively call DFS on the left and right child.

    • Backtrack by removing the current node's data after exploring both subtrees.

  3. Call the DFS function starting from the root.

  4. Return res.

๐Ÿงฎ Time and Auxiliary Space Complexity

  • Expected Time Complexity: O(N), where N is the total number of nodes in the tree. We visit each node once and perform constant-time operations (adding/removing from path) at each node.

  • Expected Auxiliary Space Complexity: O(H) in the average case, where H is the height of the tree (stack space due to recursion). In the worst case (skewed tree), it can be O(N), where N is the number of nodes.

๐Ÿง  Code (C++)

class Solution {
  public:
    vector<vector<int>> Paths(Node* root) {
        vector<vector<int>> res, path;
        vector<int> curr;
        function<void(Node*)> dfs = [&](Node* n) {
            if (!n) return;
            curr.push_back(n->data);
            if (!n->left && !n->right) res.push_back(curr);
            else dfs(n->left), dfs(n->right);
            curr.pop_back();
        };
        dfs(root);
        return res;
    }
};
โšก Alternative Approaches

๐Ÿ“Š 2๏ธโƒฃ Iterative DFS using Stack

Algorithm Steps:

  1. Use a stack storing tuples of (current node, current path).

  2. On each iteration, if the node is a leaf, add the path to result.

  3. Push right and left children with updated paths.

class Solution {
  public:
    vector<vector<int>> Paths(Node* root) {
        vector<vector<int>> res;
        if (!root) return res;
        stack<pair<Node*, vector<int>>> st;
        st.push(make_pair(root, vector<int>{root->data}));
        while (!st.empty()) {
            pair<Node*, vector<int>> curr = st.top(); st.pop();
            Node* node = curr.first;
            vector<int> path = curr.second;
            if (!node->left && !node->right) {
                res.push_back(path);
            }
            if (node->right) {
                vector<int> rightPath = path;
                rightPath.push_back(node->right->data);
                st.push(make_pair(node->right, rightPath));
            }
            if (node->left) {
                vector<int> leftPath = path;
                leftPath.push_back(node->left->data);
                st.push(make_pair(node->left, leftPath));
            }
        }
        return res;
    }
};

โœ… Why This Approach?

  • Avoids recursion overhead.

  • Handles deep trees better if stack size is a concern.

๐Ÿ“ Complexity Analysis:

  • Expected Time Complexity: O(N)

  • Expected Auxiliary Space Complexity: O(H) in average case, O(N) worst case (stack and path copies)

๐Ÿ†š Comparison of Approaches

Approach

โฑ๏ธ Time

๐Ÿ—‚๏ธ Space

โœ… Pros

โš ๏ธ Cons

Recursive DFS

๐ŸŸข O(N)

๐ŸŸข O(H)

Clean, simple, tail-recursive friendly

Recursion stack risk on deep trees

Iterative DFS (stack)

๐ŸŸข O(N)

๐ŸŸ  O(N)

No recursion limit, depth control

Slightly more verbose

โœ… Best Choice?

Scenario

Recommended Approach

โœ… Concise, clean implementation

๐Ÿฅ‡ Recursive DFS

โœ… Avoid recursion stack

๐Ÿฅˆ Iterative DFS (stack)

๐Ÿง‘โ€๐Ÿ’ป Code (Java)

class Solution {
    public static ArrayList<ArrayList<Integer>> Paths(Node r) {
        var res = new ArrayList<ArrayList<Integer>>();
        var curr = new ArrayList<Integer>();
        dfs(r, res, curr);
        return res;
    }
    static void dfs(Node n, ArrayList<ArrayList<Integer>> res, ArrayList<Integer> curr) {
        if (n == null) return;
        curr.add(n.data);
        if (n.left == null && n.right == null) res.add(new ArrayList<>(curr));
        else {
            dfs(n.left, res, curr);
            dfs(n.right, res, curr);
        }
        curr.remove(curr.size() - 1);
    }
}

๐Ÿ Code (Python)

class Solution:
    def Paths(self, root):
        res, curr = [], []
        def dfs(n):
            if not n: return
            curr.append(n.data)
            if not n.left and not n.right:
                res.append(curr.copy())
            else:
                dfs(n.left); dfs(n.right)
            curr.pop()
        dfs(root)
        return res

๐Ÿง  Contribution and Support

For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: ๐Ÿ“ฌ Any Questions?. Letโ€™s make this learning journey more collaborative!

โญ If you find this helpful, please give this repository a star! โญ


๐Ÿ“Visitor Count

Last updated