Leetcode 684. Redundant Connection

684. Redundant Connection

题目

In this problem, a tree is an undirected graph that is connected and has no cycles.

The given input is a graph that started as a tree with N nodes (with distinct values 1, 2, …, N), with one additional edge added. The added edge has two different vertices chosen from 1 to N, and was not an edge that already existed.

The resulting graph is given as a 2D-array of edges. Each element of edges is a pair [u, v] with u < v, that represents an undirected edge connecting nodes u and v.

Return an edge that can be removed so that the resulting graph is a tree of N nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array. The answer edge [u, v] should be in the same format, with u < v.

Example 1:

Input: [[1,2], [1,3], [2,3]]
Output: [2,3]
Explanation: The given undirected graph will be like this:
  1
 / \
2 - 3

Example 2:

Input: [[1,2], [2,3], [3,4], [1,4], [1,5]]
Output: [1,4]
Explanation: The given undirected graph will be like this:
5 - 1 - 2
    |   |
    4 - 3

Note:

The size of the input 2D-array will be between 3 and 1000.

Every integer represented in the 2D-array will be between 1 and N, where N is the size of the input array.

解题思路

注意到,这个图中有N个节点和N条不相同的边,因此只多了一条边。可以采取逐个向图中添加边的方式,当添加一条边形成环时,去掉这个环中任意一条边都能形成树。而题目要求我们返回最后给出的边,因此,当添加一条边将形成环时,这条边就是我们要的答案。

方法一:DFS

创建一个空图,逐渐添加边。在每次添加边前,通过dfs检查这条边的2个节点是否已经连通。如果已连通,那么已连通的路径加上要添加的边将行成环,因此这条边就是最后多余的边;如果未连通,则将这条边添加进图中。

实现细节:可以采用邻接表的形式储存图,在dfs时,添加一个pre参数,使得节点跳过父节点,只遍历子节点。

代码如下:

class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        unordered_map<int, unordered_set<int>> graph;
        for (auto edge : edges) {
        	if (dfs(graph, edge[0], edge[1], 0)) return edge;
        	graph[edge[0]].insert(edge[1]);
        	graph[edge[1]].insert(edge[0]);
        }
        return {};
    }

    bool dfs(unordered_map<int, unordered_set<int>> &graph, int src, int dst, int pre) {
    	if (graph[src].count(dst)) return true;
    	for (int nei : graph[src]) {
    		if (nei != pre) {
    			if (dfs(graph, nei, dst, src)) return true;
    		}
    	}
    	return false;
    }
};

方法二:Union-Find(quick-union)

与方法一一样,逐渐向图中添加边,直到要添加的边将形成环。不同的是,添加边不是直接添加这两个节点连成的边,而是连接这两个节点所属的树的根节点,并将其中一个根节点作为新树的根节点(直接添加边和添加这两个节点的根节点的边效果相同,都是形成一棵新树,即连通的无环图)。我们为每个节点维护一个pre先前节点,先前节点为0的节点是根节点。初始时,有N个只有根节点的树,添加边就是该边2个节点的根节点相互连接,形成新的树,而节点的根节点可以通过pre先前节点逐渐找到。如果要添加的边的2个节点的根节点相同,说明这两个节点已经连通,因此这条边就是多余的边。

代码如下:

class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        vector<int> pre(2001, 0);
        for (auto edge : edges) {
            int rootx = find(pre, edge[0]);
            int rooty = find(pre, edge[1]);
            if (rootx == rooty) return edge;
            pre[rootx] = rooty;

        }
        return {};
    }
    int find(vector<int>& pre, int i) {
        while (pre[i] != 0) {
            i = pre[i];
        }
        return i;
    }
};

方法三:Union-Find(Weighted Union-Find)

方法二将两棵树根节点连接时,总是取第一棵树的根节点作为新的根节点,可能会导致树不平衡而增大找到节点的根节点的时间。因此可以给每个节点维护一个size,记录以该节点作为根节点的树的节点数目。连接两棵树时,选取size大的树的根节点作为新的根节点。

代码如下:

class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        vector<int> pre(2001, 0);
        vector<int> size(2001, 1);
        for (auto edge : edges) {
            int rootx = find(pre, edge[0]);
            int rooty = find(pre, edge[1]);
            if (rootx == rooty) return edge;
            if (size[rootx] >= size[rooty]){
            	pre[rooty] = rootx;
            	size[rootx] += size[rooty];
            } else {
            	pre[rootx] = rooty;
            	size[rooty] += size[rootx];
            }
        }
        return {};
    }
    int find(vector<int>& pre, int i) {
        while (pre[i] != 0) {
            i = pre[i];
        }
        return i;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值