Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1443. Minimum Time to Collect All Apples in a Tree - Leetcode Solution

Problem Description

You are given an undirected tree with n nodes labeled from 0 to n - 1. The tree is represented by an array edges where each element is a pair [a, b] indicating an edge between nodes a and b. You are also given a boolean array hasApple where hasApple[i] is true if node i has an apple, and false otherwise.

Starting at the root node 0, your task is to collect all apples in the tree. Each time you move along an edge, it takes 1 second. You can move along the same edge multiple times if needed.

Return the minimum time (in seconds) it takes to collect all apples and return to the root node 0.

  • Each edge traversed counts for 1 second, regardless of direction.
  • You must return to the root after collecting all apples.
  • There is exactly one path between any two nodes (since it's a tree).
  • If there are no apples, return 0.

Thought Process

The first step is to recognize that we only need to traverse to branches that contain apples or lead to apples. Traversing to a node without apples (and without any apples in its subtree) is unnecessary.

A brute-force approach would be to visit every node and check for apples, but this is inefficient. Instead, we can use a Depth-First Search (DFS) to recursively explore each subtree, only counting the time to traverse edges that are necessary to collect apples.

For every subtree, if there is at least one apple in it, we must travel to and from that subtree (which costs 2 seconds per edge). If not, we do not need to visit that branch at all.

Solution Approach

  • Build the Tree: Use the edges list to build an adjacency list representation of the tree. This allows easy traversal.
  • DFS Traversal: Define a recursive DFS function that, for each node, recursively explores its children (excluding the parent to avoid cycles).
  • Check for Apples: For each child, if the subtree rooted at that child contains any apples, add the cost (2 seconds) to reach and return from that child.
  • Return the Total Time: The total time is the sum of all necessary traversals. If the root itself has no apples and all its subtrees also have none, return 0.
  • Edge Cases: If there are no apples in the entire tree, the answer is 0.

This approach ensures we only traverse the minimal set of edges necessary to collect all apples and return to the root.

Example Walkthrough

Example:
n = 7
edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]]
hasApple = [false, false, true, false, true, true, false]

  1. Build the tree:
    • Node 0 connects to 1 and 2
    • Node 1 connects to 4 and 5
    • Node 2 connects to 3 and 6
  2. DFS from root 0:
    • Explore child 1:
      • Explore 4: has apple → need to visit (cost 2 seconds)
      • Explore 5: has apple → need to visit (cost 2 seconds)
      • Node 1 itself has no apple, but its children do → total cost from 1 is 4 seconds
    • Explore child 2:
      • Explore 3: no apple
      • Explore 6: no apple
      • Node 2 itself has apple → need to visit (cost 2 seconds)
    • Total cost from 2 is 2 seconds
  3. Total time = 4 (from 1) + 2 (from 2) = 6 seconds.

Code Implementation

class Solution:
    def minTime(self, n, edges, hasApple):
        from collections import defaultdict
        tree = defaultdict(list)
        for a, b in edges:
            tree[a].append(b)
            tree[b].append(a)

        def dfs(node, parent):
            time = 0
            for child in tree[node]:
                if child != parent:
                    child_time = dfs(child, node)
                    if child_time > 0 or hasApple[child]:
                        time += child_time + 2
            return time

        result = dfs(0, -1)
        return result
      
class Solution {
public:
    int minTime(int n, vector<vector<int>>& edges, vector<bool>& hasApple) {
        vector<vector<int>> tree(n);
        for (auto& e : edges) {
            tree[e[0]].push_back(e[1]);
            tree[e[1]].push_back(e[0]);
        }
        return dfs(0, -1, tree, hasApple);
    }
    
    int dfs(int node, int parent, vector<vector<int>>& tree, vector<bool>& hasApple) {
        int time = 0;
        for (int child : tree[node]) {
            if (child != parent) {
                int child_time = dfs(child, node, tree, hasApple);
                if (child_time > 0 || hasApple[child]) {
                    time += child_time + 2;
                }
            }
        }
        return time;
    }
};
      
class Solution {
    public int minTime(int n, List<List<Integer>> edges, List<Boolean> hasApple) {
        List<List<Integer>> tree = new ArrayList<>();
        for (int i = 0; i < n; i++) tree.add(new ArrayList<>());
        for (List<Integer> e : edges) {
            tree.get(e.get(0)).add(e.get(1));
            tree.get(e.get(1)).add(e.get(0));
        }
        return dfs(0, -1, tree, hasApple);
    }
    
    private int dfs(int node, int parent, List<List<Integer>> tree, List<Boolean> hasApple) {
        int time = 0;
        for (int child : tree.get(node)) {
            if (child != parent) {
                int childTime = dfs(child, node, tree, hasApple);
                if (childTime > 0 || hasApple.get(child)) {
                    time += childTime + 2;
                }
            }
        }
        return time;
    }
}
      
var minTime = function(n, edges, hasApple) {
    const tree = Array.from({length: n}, () => []);
    for (const [a, b] of edges) {
        tree[a].push(b);
        tree[b].push(a);
    }
    function dfs(node, parent) {
        let time = 0;
        for (const child of tree[node]) {
            if (child !== parent) {
                const childTime = dfs(child, node);
                if (childTime > 0 || hasApple[child]) {
                    time += childTime + 2;
                }
            }
        }
        return time;
    }
    return dfs(0, -1);
};
      

Time and Space Complexity

  • Brute-force Approach: If you visited every path to every apple separately, time complexity could be as bad as O(n2), especially if repeatedly traversing the same branches.
  • Optimized DFS Approach:
    • Time Complexity: O(n), since each node and edge is visited once in the DFS traversal.
    • Space Complexity: O(n), due to the adjacency list and recursion stack.

The optimized approach is efficient because it avoids redundant traversals and only explores necessary branches.

Summary

This problem is elegantly solved by recognizing that only subtrees containing apples need to be visited. By using DFS, we can efficiently accumulate the minimal time needed to collect all apples and return to the root. The key insight is to avoid unnecessary paths and only count the cost for branches where apples are present. This results in a clean, optimal O(n) solution.