多叉树题目:收集树上所有苹果的最少时间

题目

标题和出处

标题:收集树上所有苹果的最少时间

出处:1443. 收集树上所有苹果的最少时间

难度

6 级

题目描述

要求

给定一个有 n \texttt{n} n 个结点的无向树,结点编号为 0 \texttt{0} 0 n − 1 \texttt{n} - \texttt{1} n1,有一些结点有苹果。通过树上的一条边需要花费 1 \texttt{1} 1 秒钟。计算从结点 0 \texttt{0} 0 出发收集所有苹果并回到结点 0 \texttt{0} 0 的最少时间的秒数并返回。

无向树的边由 edges \texttt{edges} edges 给出,其中 edges[i]   =   [a i ,   b i ] \texttt{edges[i] = [a}_\texttt{i}\texttt{, b}_\texttt{i}\texttt{]} edges[i] = [ai, bi],表示有一条边连接 a i \texttt{a}_\texttt{i} ai b i \texttt{b}_\texttt{i} bi。除此以外,还有一个布尔数组 hasApple \texttt{hasApple} hasApple,其中 hasApple[i]   =   true \texttt{hasApple[i] = true} hasApple[i] = true 表示结点 i \texttt{i} i 有一个苹果,否则结点 i \texttt{i} i 没有苹果。

示例

示例 1:

示例 1

输入: n   =   7,   edges   =   [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]],   hasApple   =   [false,false,true,false,true,true,false] \texttt{n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,true,true,false]} n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,true,true,false]
输出: 8 \texttt{8} 8
解释:上图展示了给定的树,其中红色结点表示有苹果。一个能收集到所有苹果的最优方案由绿色箭头表示。

示例 2:

示例 2

输入: n   =   7,   edges   =   [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]],   hasApple   =   [false,false,true,false,false,true,false] \texttt{n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,false,true,false]} n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,false,true,false]
输出: 6 \texttt{6} 6
解释:上图展示了给定的树,其中红色结点表示有苹果。一个能收集到所有苹果的最优方案由绿色箭头表示。

示例 3:

输入: n   =   7,   edges   =   [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]],   hasApple   =   [false,false,false,false,false,false,false] \texttt{n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,false,false,false,false,false]} n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,false,false,false,false,false]
输出: 0 \texttt{0} 0

数据范围

  • 1 ≤ n ≤ 10 5 \texttt{1} \le \texttt{n} \le \texttt{10}^\texttt{5} 1n105
  • edges.length = n − 1 \texttt{edges.length} = \texttt{n} - \texttt{1} edges.length=n1
  • edges[i].length = 2 \texttt{edges[i].length} = \texttt{2} edges[i].length=2
  • 0 ≤ from i ,   to i ≤ n − 1 \texttt{0} \le \texttt{from}_\texttt{i}\texttt{, to}_\texttt{i} \le \texttt{n} - \texttt{1} 0fromi, toin1
  • from i < to i \texttt{from}_\texttt{i} < \texttt{to}_\texttt{i} fromi<toi
  • hasApple.length = n \texttt{hasApple.length} = \texttt{n} hasApple.length=n

解法

思路和算法

这道题中的树使用无向边表示,规定根结点是结点 0 0 0,其余结点之间只能知道连通关系。为了得到相邻结点之间的父结点和子结点的关系,需要根据给定的边得到每个结点的相邻结点,然后从根结点开始遍历树。在确定所有相邻结点之间的父结点和子结点的关系之后,即可得到树的结构,包括每个结点的父结点和子结点,然后计算收集树上所有苹果的最少时间。

可以使用深度优先搜索得到树的结构。从根结点开始遍历,遍历过程中需要知道相邻结点之间的父结点和子结点的关系。由于和一个结点相邻的结点只有该结点的父结点和全部子结点,一种方法是在遍历过程中传入当前结点的父结点编号,在遍历与当前结点相邻的结点时跳过父结点,则可确保只会访问当前结点的子结点。

为了将收集苹果的时间降到最低,对于树中的每个苹果,应该考虑最短路径,即从根结点直接到达苹果所在结点的路径。当树中有多个苹果时,收集苹果的过程中应该避免重复路径,即同一条边最多在两个方向上各走一次。例如,示例 1 中收集树上所有苹果需要经过的边包括 [ 0 , 1 ] [0, 1] [0,1] [ 1 , 4 ] [1, 4] [1,4] [ 1 , 5 ] [1, 5] [1,5] [ 0 , 2 ] [0, 2] [0,2],共有 4 4 4 条边,最少时间的方案下每条边走 2 2 2 次,因此最少时间是 8 8 8

基于上述分析,为了计算最少时间,需要计算收集树上所有苹果需要经过的最少边数,将边数乘以 2 2 2 即为最少时间。

计算过程中,为了避免重复计算,需要记录每个结点是否访问过,初始时只有根结点 0 0 0 被访问过。对于每个有苹果的结点,从该结点出发向根结点移动,将经过的每个结点(包括该结点本身)的状态都设为被访问过,当遇到一个已经访问过的结点时结束当前结点的移动。如果遇到一个已经访问过的结点 x x x,则从结点 x x x 到根结点的路径上的所有边都已经访问过,因此不应重复访问。

对于全部有苹果的结点计算结束之后,即可得到收集树上所有苹果需要经过的最少边数。

实现方面,可以在计算边数的同时计算最少时间,对于遍历到的每条边,将时间加 2 2 2

代码

class Solution {
    List<Integer>[] adjacentNodes;
    int[] parents;
    boolean[] visited;

    public int minTime(int n, int[][] edges, List<Boolean> hasApple) {
        adjacentNodes = new List[n];
        for (int i = 0; i < n; i++) {
            adjacentNodes[i] = new ArrayList<Integer>();
        }
        for (int[] edge : edges) {
            int node0 = edge[0], node1 = edge[1];
            adjacentNodes[node0].add(node1);
            adjacentNodes[node1].add(node0);
        }
        parents = new int[n];
        Arrays.fill(parents, -1);
        dfs(0, -1);
        visited = new boolean[n];
        visited[0] = true;
        int time = 0;
        for (int i = 0; i < n; i++) {
            if (hasApple.get(i)) {
                time += getTime(i);
            }
        }
        return time;
    }

    public void dfs(int node, int parent) {
        List<Integer> adjacent = adjacentNodes[node];
        for (int next : adjacent) {
            if (next == parent) {
                continue;
            }
            parents[next] = node;
            dfs(next, node);
        }
    }

    public int getTime(int node) {
        int time = 0;
        while (!visited[node]) {
            visited[node] = true;
            node = parents[node];
            time += 2;
        }
        return time;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是树的结点数。深度优先搜索的过程中每个结点访问一次,计算最少时间的过程中每条边最多遍历一次,共有 n n n 个结点和 n − 1 n - 1 n1 条边,因此时间复杂度是 O ( n ) O(n) O(n)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是树的结点数。空间复杂度包括存储相邻结点信息的空间、记录每个结点的父结点信息的空间和递归调用的栈空间,存储相邻结点信息的空间和记录每个结点的父结点信息的空间是 O ( n ) O(n) O(n),递归调用的栈空间在最坏情况下是 O ( n ) O(n) O(n),因此空间复杂度是 O ( n ) O(n) O(n)

  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值