在本问题中, 树指的是一个连通且无环的无向图。
输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
结果图是一个以边
组成的二维数组。每一个边
的元素是一对[u, v]
,满足 u < v
,表示连接顶点u
和v
的无向图的边。
返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v]
应满足相同的格式 u < v
。
示例 1:
输入: [[1,2], [1,3], [2,3]] 输出: [2,3] 解释: 给定的无向图为: 1 / \ 2 - 3
示例 2:
输入: [[1,2], [2,3], [3,4], [1,4], [1,5]] 输出: [1,4] 解释: 给定的无向图为: 5 - 1 - 2 | | 4 - 3
注意:
更新(2017-09-26):
我们已经重新检查了问题描述及测试用例,明确图是无向 图。对于有向图详见冗余连接II。对于造成任何不便,我们深感歉意。
思路:
方法一:采用bfs的思想,每加一个边到节点中就检查是否存在环路,如果存在环路就返回。检测是否存在环路算法如下:
利用bfs的思想,判断当前节点对(比如[1,2])以1为起点,2为终点,是否已存在路径从1开始到2结束。但是这样做时间消耗较大,因为对每一个节点对都要做广度优先遍历,且还要控制避免出现死循环[1,2]和[2,1]。代码如下:
bool hasCircle(int cur,int target, unordered_map> &m,int pre) {
if (m[cur].count(target)) {
return true;
}
for (auto tmp : m[cur]) {
if (tmp == pre) {
continue;
}
if (hasCircle(tmp, target, m, cur)) {
return true;
}
}
return false;
}
vector findRedundantConnection(vector>& edges) {
unordered_map> m;
for (auto edge : edges) {
if (hasCircle(edge[0], edge[1], m, -1)) {
return edge;
}
m[edge[0]].insert(edge[1]);
m[edge[1]].insert(edge[0]);
}
return {};
}
方法二:
利用union find的思想,用一个邻接表表示相关关系,建立一个对应关系,a[i]=j表示i可以连接到j,具体union find原理深入了解了在做详细解释,这里只解释怎么用。对于任何一个数组对[i,j],如果经过映射关系最后返回的x和y都是相同的值,则证明这组数组对就是待删除的边(这里讲的比较乱,下面给一个例子),首先映射函数如下:
int find(vector &vec, int i) {
while (vec[i] != -1) {
i = vec[i];
}
return i;
}
映射函数如上如所示,对于题目的例子:
1 / \ 2 - 3输入
[1,2]
对数组对每一个数都应用映射函数,则映射函数返回的结果图示为:
1 / 2
再输入
[1,3]
对于1,映射函数返回2,对于3,映射函数返回3,由于2!=3,所以加到映射关系里a[2]=3,结果更新为:
1 / 2 ——3
再输入
[2,3]
2映射为3,3映射为3,3==3所以[2,3]这个数组对如果加上就会形成环,所以答案是[2,3]。union find换一种思路理解就是通过单向链表的关系,把所有能到达的节点通过链表连接起来,如果这时候数组对的两个值映射返回相同的值,意味着就算不加这个数组对也能实现两个点的连通查找,如果在两个节点间再加这个数组对,就会形成环。
代码如下:
int find(vector &vec, int i) {
while (vec[i] != -1) {
i = vec[i];
}
return i;
}
vector findRedundantConnection(vector>& edges) {
vector vec(1001, -1);
for (auto edge : edges) {
int x = find(vec, edge[0]);
int y = find(vec, edge[1]);
if (x == y) {
return edge;
}
vec[x] = y;
}
return {};
}