leetcode
leetcode 2301 ~ 2350
收集树中金币

收集树中金币

难度:

标签:

题目描述

There exists an undirected and unrooted tree with n nodes indexed from 0 to n - 1. You are given an integer n and a 2D integer array edges of length n - 1, where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the tree. You are also given an array coins of size n where coins[i] can be either 0 or 1, where 1 indicates the presence of a coin in the vertex i.

Initially, you choose to start at any vertex in the tree. Then, you can perform the following operations any number of times: 

  • Collect all the coins that are at a distance of at most 2 from the current vertex, or
  • Move to any adjacent vertex in the tree.

Find the minimum number of edges you need to go through to collect all the coins and go back to the initial vertex.

Note that if you pass an edge several times, you need to count it into the answer several times.

 

Example 1:

Input: coins = [1,0,0,0,0,1], edges = [[0,1],[1,2],[2,3],[3,4],[4,5]]
Output: 2
Explanation: Start at vertex 2, collect the coin at vertex 0, move to vertex 3, collect the coin at vertex 5 then move back to vertex 2.

Example 2:

Input: coins = [0,0,0,1,1,0,0,1], edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[5,6],[5,7]]
Output: 2
Explanation: Start at vertex 0, collect the coins at vertices 4 and 3, move to vertex 2,  collect the coin at vertex 7, then move back to vertex 0.

 

Constraints:

  • n == coins.length
  • 1 <= n <= 3 * 104
  • 0 <= coins[i] <= 1
  • edges.length == n - 1
  • edges[i].length == 2
  • 0 <= ai, bi < n
  • ai != bi
  • edges represents a valid tree.

代码结果

运行时间: 197 ms, 内存: 28.1 MB


// Java Stream solution for the problem
// Problem statement: Given an unrooted tree with n nodes, each node may have a coin. You need to collect all the coins starting from any node and return to the starting node with the minimum number of edges traversed.

import java.util.*;
import java.util.stream.Collectors;

public class SolutionStream {
    private Map<Integer, List<Integer>> graph;
    private int[] coins;
    private int minEdges = 0;

    public int collectTheCoins(int[] coins, int[][] edges) {
        int n = coins.length;
        this.coins = coins;
        graph = Arrays.stream(edges)
                      .flatMap(edge -> Stream.of(Map.entry(edge[0], edge[1]), Map.entry(edge[1], edge[0])))
                      .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
        boolean[] visited = new boolean[n];
        dfs(0, visited);
        return minEdges;
    }

    private boolean dfs(int node, boolean[] visited) {
        visited[node] = true;
        boolean hasCoin = coins[node] == 1;
        for (int neighbor : graph.getOrDefault(node, Collections.emptyList())) {
            if (!visited[neighbor]) {
                boolean childHasCoin = dfs(neighbor, visited);
                if (childHasCoin) {
                    minEdges += 2;
                    hasCoin = true;
                }
            }
        }
        return hasCoin;
    }
}

解释

方法:

题解的思路是通过拓扑排序来去除树中那些对解决问题没有直接帮助的节点,即那些没有金币且只有一个邻居(叶节点)的节点。首先,构建邻接表和每个节点的邻居数。然后,将所有没有金币的叶子节点加入队列,并通过拓扑排序逐个移除这些节点。经过一轮拓扑排序后,对剩下的树再进行一轮拓扑排序,特别处理有金币的叶子节点。最后,根据剩余的边数计算答案(来回各一次,所以乘以2),返回需要经过的最少边数。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在题解中,你是如何确定哪些节点是'没有直接帮助的节点'?是否有一些特定的标准或条件来定义这些节点?
在题解中,被定义为'没有直接帮助的节点'的是那些没有金币且只有一个邻居(即叶节点)的节点。这些节点被认为对收集金币的最终目标没有贡献。因此,在拓扑排序的过程中,首先将这些节点识别并从图中移除,这样可以简化问题规模,同时减少不必要的遍历。
🦆
拓扑排序通常用于处理有向图中的依赖关系。在这个无向树的问题中,拓扑排序是如何应用的?具体是如何操作的?
虽然拓扑排序通常用于有向图,但在这个问题中,它被用来逐层剥离无向树的外围叶节点。具体操作是,首先建立每个节点的邻接关系和邻居计数。然后,将所有无金币的叶节点(只有一个邻居的节点)加入队列,并逐一从队列中移除这些节点,同时更新其邻居的邻居计数。如果邻居节点因此变成了新的叶节点,且也没有金币,它们也会被加入队列进行后续移除。这种方法可以看作是树的层次剥离过程,逐步减少树的大小。
🦆
题解中提到了'对有金币的叶子节点进行特别处理',能详细解释一下这种处理的具体逻辑和必要性吗?
在拓扑排序后,对于那些依然保留有金币的叶节点,需要进行特别处理。这些节点是收集金币过程中的关键节点,因为它们是路径的必经之地。特别处理的逻辑是,将这些有金币的叶节点视为新的起点,再次执行拓扑排序,继续移除那些因为这个过程变成新的叶节点的节点。这是必要的,因为它确保了所有有金币的节点都被考虑在内,并且优化了必须经过的路径长度。
🦆
在进行拓扑排序时,如果一个节点既有金币又只有一个邻居,这种情况下的处理策略是什么?
如果一个节点既有金币又只有一个邻居,这个节点不会在初次拓扑排序中被移除,因为它含有金币是必需的。在后续处理中,这类节点会被保留,并作为重要节点对待,因为它们是搜集路径的关键部分。即使它们是叶节点,也需要保留,因为它们对整体任务来说是价值节点。

相关问题