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
.
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.
edges
list to build an adjacency list representation of the tree. This allows easy traversal.
This approach ensures we only traverse the minimal set of edges necessary to collect all apples and return to the root.
Example:
n = 7
edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]]
hasApple = [false, false, true, false, true, true, false]
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);
};
The optimized approach is efficient because it avoids redundant traversals and only explores necessary branches.
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.