leetcode
leetcode 2051 ~ 2100
受限条件下可到达节点的数目

受限条件下可到达节点的数目

难度:

标签:

题目描述

现有一棵由 n 个节点组成的无向树,节点编号从 0n - 1 ,共有 n - 1 条边。

给你一个二维整数数组 edges ,长度为 n - 1 ,其中 edges[i] = [ai, bi] 表示树中节点 aibi 之间存在一条边。另给你一个整数数组 restricted 表示 受限 节点。

在不访问受限节点的前提下,返回你可以从节点 0 到达的 最多 节点数目

注意,节点 0 会标记为受限节点。

 

示例 1:

输入:n = 7, edges = [[0,1],[1,2],[3,1],[4,0],[0,5],[5,6]], restricted = [4,5]
输出:4
解释:上图所示正是这棵树。
在不访问受限节点的前提下,只有节点 [0,1,2,3] 可以从节点 0 到达。

示例 2:

输入:n = 7, edges = [[0,1],[0,2],[0,5],[0,4],[3,2],[6,5]], restricted = [4,2,1]
输出:3
解释:上图所示正是这棵树。
在不访问受限节点的前提下,只有节点 [0,5,6] 可以从节点 0 到达。

 

提示:

  • 2 <= n <= 105
  • edges.length == n - 1
  • edges[i].length == 2
  • 0 <= ai, bi < n
  • ai != bi
  • edges 表示一棵有效的树
  • 1 <= restricted.length < n
  • 1 <= restricted[i] < n
  • restricted 中的所有值 互不相同

代码结果

运行时间: 123 ms, 内存: 59.6 MB


// Java Stream solution
// 思路:
// 虽然Java Stream更适合数据流处理,但我们依然可以通过组合多个Stream操作来实现DFS。
// 不过,由于DFS涉及到递归或显式的栈操作,Stream的优势并不明显。
// 这里我们主要还是利用Stream对集合进行过滤和遍历。

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

public class Solution {
    public int reachableNodes(int n, int[][] edges, int[] restricted) {
        List<List<Integer>> graph = IntStream.range(0, n)
                .mapToObj(i -> new ArrayList<Integer>())
                .collect(Collectors.toList());
        for (int[] edge : edges) {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]);
        }

        Set<Integer> restrictedSet = Arrays.stream(restricted).boxed().collect(Collectors.toSet());
        boolean[] visited = new boolean[n];

        return dfs(0, graph, visited, restrictedSet);
    }

    private int dfs(int node, List<List<Integer>> graph, boolean[] visited, Set<Integer> restrictedSet) {
        if (restrictedSet.contains(node) || visited[node]) {
            return 0;
        }
        visited[node] = true;
        return 1 + graph.get(node).stream()
                .filter(neighbor -> !visited[neighbor] && !restrictedSet.contains(neighbor))
                .mapToInt(neighbor -> dfs(neighbor, graph, visited, restrictedSet))
                .sum();
    }
}

解释

方法:

题解采用了深度优先搜索(DFS)的方法来解决该问题。首先,通过使用图(以邻接表的形式)来表示树的结构,从而避免访问受限节点。然后,从节点0开始,递归地遍历所有可到达的节点,并计算可到达节点的总数。在建图过程中,如果边的任一端点是受限节点,则该边不被添加到图中。DFS函数通过递归的方式访问所有子节点(排除了父节点以防止返回),并累计可以到达的节点数。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在构建图时,为什么选择邻接表而不是邻接矩阵来表示树的结构?
在树或稀疏图的情况下,选择邻接表而不是邻接矩阵可以更加高效。邻接表相比于邻接矩阵,可以节省大量空间,尤其是当图中边的数量远少于节点数的平方时。此外,使用邻接表,可以更快地遍历到每个节点的直接邻居,因为它直接存储了每个节点的邻接节点,而不是存储整个节点间的关系矩阵,这在DFS等图遍历算法中特别有用,可以提高效率。
🦆
如何确保DFS过程中不会重复访问已经访问过的节点?
在DFS过程中,为了避免重复访问节点,通常会使用一个标记(如访问数组或集合)来记录哪些节点已被访问。在题解中,通过传递父节点的方法来避免回到父节点,从而防止无限循环,这种方法特别适用于树结构,因为树中任意两个节点间只有唯一路径,即不存在环。每次从一个节点向其子节点递归时,都会排除向回到该节点的父节点的可能,确保每个节点只被访问一次。
🦆
在DFS函数中,`fa`参数的作用是什么?为什么需要有这个参数?
在DFS函数中,`fa`参数代表当前节点的父节点。这个参数的主要作用是防止在遍历过程中返回到父节点,从而避免造成无限循环。在树(特别是无向树)中,每个节点都可以通过任意一条边连接到其邻居节点,如果不记录父节点,节点在递归过程中可能会反复访问父节点,导致错误的循环递归。因此,通过记录每个节点的父节点(`fa`),可以在每次递归调用时检查并跳过向父节点的访问,确保DFS的正确进行。

相关问题