[路飞]_leetcode_685. 冗余连接 II

287 阅读1分钟

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

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

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

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

示例 1:

    1 -----> 3
    |        ^
    |      /
    |    / 
    |  /
    2

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

示例 2:

    2 ---> 3
    ^      |
    |      \/
    1 <--- 4
    |
    \/
    5
输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
输出:[4,1]

解题思路

根据树的定义,每个节点都有一个父节点,根节点没有父节点。树的变等于节点树减1。

当向树中插入一条附加边 u -> v 会出现如下2中情况:

  1. 有环: 在树中节点构成环路
  2. 有冲突: 一个节点的入度为2,也就是有2个父亲节点 因为有环和有冲突可能是并存的,那么又可以分为如下3中情况:
  3. 有环,无冲突 如例2
  4. 有环,有冲突
    2 ---> 3
    ^      |
    |      \/
    1 <--- 4
    ^
    |
    5
  1. 无环,有冲突 如例1 如何判断有环或有冲突,我们设置一个parent数组来记录每个节点,当访问到边[u, v]
  • 如果此时已有 panrent[v] !== v, 说明v有两个父节点,将当前边标记为冲突边
  • 否则,令 panrent[v] = u, 然后在并查集中分别找到 uu 和 vv 的祖先(即各自的连通分支中的根节点),如果祖先相同,说明这条边导致环路出现,将当前的边标记为导致环路出现的边,如果祖先不同,则在并查集中将 u 和 v 进行合并。 解决方法
  1. 有环,无冲突的情况,我们只需要把构成环路的那条边删除即可
  2. 有环,有冲突的情况,会有2条指向同一个节点的边,即[u,v][parent[v],v], 因为[u,v]是导致冲突的边,所以附加边只能是[parent[v],v]
  3. 无环,有冲突的情况,只有一条附加边删除即可

代码

class UnionFind {
    constructor(n) {
        this.fa = new Array(n)
        for (let i = 0; i <= n; i++) {
            this.fa[i] = i;
        } 
    }

    find(x) {
        if (this.fa[x] == x) return x;
        let root =  this.find(this.fa[x])
        this.fa[x] = root
        return root
    }

    unit(a, b) {
        this.fa[this.find(a)] = this.find(b)
    }
}
var findRedundantDirectedConnection = function(edges) {
    let nodeCount = edges.length
    let uf = new UnionFind(nodeCount+1)

    let parent = []
    for (let i = 1; i < (nodeCount + 1); i++) {
        parent[i] = i
    }

    let conflit = -1
    let cycle = -1

    for (i in edges) {
        let edge = edges[i]
        let node1 = edge[0], node2 = edge[1]
        if (parent[node2] != node2) { // 有冲突边
            conflit = i
        } else {
            parent[node2] = node1
            if (uf.find(node1) == uf.find(node2)) { // 有环路
                cycle = i;
            } else {
                uf.unit(node1, node2)
            }
        }
    }

    if (conflit < 0) {
        return edges[cycle]
    } else {
        let conflitEdge = edges[conflit]
        if (cycle >= 0) {
            return [parent[conflitEdge[1]], conflitEdge[1]]
        } else {
            return conflitEdge
        }
    }
};