5a3c1b2d创建于 2025年8月3日历史提交
/**
 * https://leetcode.com/problems/redundant-connection/
 * Time O((V)^2 + E) | Space O(V + E)
 * @param {number[][]} edges
 * @return {number[]}
 */
var findRedundantConnection = function (edges) {
    const graph = new Array(1000 + 1).fill().map(() => []);

    for (const [src, dst] of edges) {
        const hasNodes = src in graph && dst in graph;
        if (hasNodes && hasRedundantConnection(graph, src, dst))
            return [src, dst];

        graph[src].push(dst);
        graph[dst].push(src);
    }
};

const hasRedundantConnection = (graph, source, target, seen = new Set()) => {
    if (seen.has(source)) return false;
    seen.add(source);

    const isEqual = source === target;
    if (isEqual) return true;

    return dfs(graph, source, target, seen);
};

const dfs = (graph, source, target, seen) => {
    for (const neighbor of graph[source]) {
        if (hasRedundantConnection(graph, neighbor, target, seen)) return true;
    }

    return false;
};

/**
 * https://leetcode.com/problems/redundant-connection/
 * Time O(V + E) | Space O(V + E)
 * @param {number[][]} edges
 * @return {number[]}
 */
var findRedundantConnection = function (edges) {
    return new UnionFind(edges).redundantConnection;
};

class UnionFind {
    constructor(edges) {
        this.parent = new Array(edges.length + 1)
            .fill()
            .map((_, index) => index);
        this.rank = new Array(edges.length + 1).fill(1);
        this.redundantConnection = [-1, -1];

        this.search(edges);
    }

    search(edges) {
        for (let [src, dst] of edges) {
            const hasConnection = this.union(src, dst);
            if (!hasConnection) return (this.redundantConnection = [src, dst]);
        }
    }

    find(node, { parent } = this) {
        let head = parent[node];

        const isEqual = () => head === parent[head];
        while (!isEqual()) {
            const tail = parent[parent[head]];

            this.compress(head, tail);
            head = parent[head];
        }

        return head;
    }

    compress(tail, head, { parent } = this) {
        parent[tail] = head;
    }

    increaseRank(head, tail, { rank } = this) {
        rank[head] += rank[tail];
    }

    union(src, dst, { rank } = this) {
        const [rootSrc, rootDst] = [this.find(src), this.find(dst)];

        const hasCycle = rootSrc === rootDst;
        if (hasCycle) return false;

        const isSrcGreater = rank[rootDst] < rank[rootSrc];
        if (isSrcGreater) {
            this.increaseRank(rootDst, rootSrc);
            this.compress(rootSrc, rootDst);
        }

        const isDstGreater = rank[rootSrc] <= rank[rootDst];
        if (isDstGreater) {
            this.increaseRank(rootSrc, rootDst);
            this.compress(rootDst, rootSrc);
        }

        return true;
    }
}