leetcode
leetcode 601 ~ 650
冗余连接 II

冗余连接 II

难度:

标签:

题目描述

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。

输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1n)的树及一条附加的有向边构成。附加的边包含在 1n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 uivi 的一个父节点。

返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

 

示例 1:

输入:edges = [[1,2],[1,3],[2,3]]
输出:[2,3]

示例 2:

输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
输出:[4,1]

 

提示:

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ui, vi <= n

代码结果

运行时间: 28 ms, 内存: 16.4 MB


// Problem: Given a directed graph with n nodes, represented as an edge list, where the nodes are numbered from 1 to n. The graph is initially a tree, with one additional edge added that causes a cycle or multiple parents. Our task is to find and return one edge that can be removed to form a valid tree with one root and no cycles. 
 
// Approach using Java Streams: 
// 1. Use an array to track the parent of each node. 
// 2. Traverse the edge list and detect a cycle or a node with two parents. 
// 3. If a cycle is detected, return the last edge that causes it. 
// 4. If a node has two parents, return the second edge encountered. 
 
import java.util.Arrays; 
import java.util.stream.IntStream; 
 
public class Solution { 
    public int[] findRedundantDirectedConnection(int[][] edges) { 
        int n = edges.length; 
        int[] parent = new int[n + 1]; 
        int[] candidate1 = null, candidate2 = null; 
        Arrays.fill(parent, 0); 
        for (int[] edge : edges) { 
            int u = edge[0], v = edge[1]; 
            if (parent[v] == 0) { 
                parent[v] = u; 
            } else { 
                candidate1 = new int[]{parent[v], v}; 
                candidate2 = new int[]{u, v}; 
                edge[1] = 0; 
            } 
        } 
        IntStream.range(1, n + 1).forEach(i -> parent[i] = i); 
        for (int[] edge : edges) { 
            if (edge[1] == 0) continue; 
            int u = edge[0], v = edge[1]; 
            if (find(parent, u) == v) { 
                return candidate1 == null ? edge : candidate1; 
            } 
            parent[v] = u; 
        } 
        return candidate2; 
    } 
    private int find(int[] parent, int u) { 
        return parent[u] == u ? u : (parent[u] = find(parent, parent[u])); 
    } 
}

解释

方法:

该题解使用并查集来解决问题。首先预处理边的入度数组,记录每个节点被指向的次数。然后按照特定顺序尝试删除边,判断删除后的图是否会形成环。如果删除构成入度为2的边不会形成环,说明这条边是导致冗余的边;如果删除构成入度为1的边不会形成环,说明这条边是导致冗余的边。在并查集中,如果合并两个节点时发现它们已经在同一个集合内,说明形成了环。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在题解中提到的并查集中的`路径压缩`技术是如何帮助优化查找效率的?
路径压缩技术是并查集优化中的一个重要手段。在查找元素根节点的过程中,路径压缩将元素到根节点路径上的所有节点直接连接到根节点。这样做的目的是减少后续查找同一路径上节点的深度,从而减少递归或迭代的层数,加快查找速度。经过多次操作之后,结构趋于扁平化,使得任何节点的根节点都可以近乎直接访问,显著降低了查找时间复杂度。
🦆
题解中未详细解释如何处理入度为2的节点的两条边,具体是如何判断哪一条边应当被删除以解决冗余连接问题?
在处理入度为2的节点时,题解采用了尝试删除每一条边的策略来判断哪一条边应该被删除。具体步骤是:首先从后向前遍历所有边,寻找到入度为2的节点相关的两条边。然后,尝试分别删除这两条边,每次删除后都使用并查集检查剩余的图是否会形成环。如果删除某条边后图不形成环,则这条边就是冗余的,应该被删除。这一策略利用了图理论中的性质——如果删除某条边后仍然能保持图的连通性且无环,则该边可能是冗余的。
🦆
在使用并查集判断形成环的逻辑中,为何合并操作返回False表示已经形成环,这背后的逻辑是什么?
在并查集中,每个节点开始时都是自己的根节点。当尝试合并两个节点时,将它们的根节点设为同一个,以表示它们属于同一个连通分量。如果在合并操作中发现两个节点已经有相同的根节点,这意味着这两个节点已经在同一个连通分量中,再次尝试将它们合并就会形成一个环。因此,如果合并操作返回False,表明在尝试合并两个已经连通的节点,从而发现了一个环。这是检测图中环的有效方法。
🦆
题解中提到删除某条边后判断是否形成环,这一步骤在实现上是如何确保删除的边确实不被考虑在内的?
在实现上,题解通过一个简单的策略来确保删除的边不被考虑:在遍历并尝试合并其他边时,跳过指定的删除边。具体来说,在执行并查集的合并操作前,检查当前正在处理的边是否是要删除的边的索引。如果是,则跳过这条边,不进行任何操作;如果不是,则正常执行合并操作。这样可以确保在构建并查集的过程中,被标记为删除的边不会影响并查集的结构,从而准确地检验删除这条边后图是否还会形成环。

相关问题

冗余连接

树可以看成是一个连通且 无环 的 无向 图。

给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 aibi 之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的那个。

 

示例 1:

输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]

示例 2:

输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]

 

提示:

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ai < bi <= edges.length
  • ai != bi
  • edges 中无重复元素
  • 给定的图是连通的