在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
输入一个有向图,该图由一个有着 n
个节点(节点值不重复,从 1
到 n
)的树及一条附加的有向边构成。附加的边包含在 1
到 n
中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges
。 每个元素是一对 [ui, vi]
,用以表示 有向 图中连接顶点 ui
和顶点 vi
的边,其中 ui
是 vi
的一个父节点。
返回一条能删除的边,使得剩下的图是有 n
个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。
简单来说就是有n个节点n条边的有向图,需要找到最后加入的一条删除后可以构成有向树的边。
今天这题目比昨天的难不少,或者说复杂不少,因为需要分情况考虑了,昨天的代码直接贴过来都会有测试用例过不去。
显然,一棵由有向图构成的树在增加一条边之后,会有两种可能。第一种情况,新增的边连接了根节点,不存在入度为0的根节点了。第二种情况,新增的边连接的两个非根节点,出现了一个入度为2的节点。
对于第一种情况,和昨天的做法是一样的,一条条加边,找到一条连接的两个点在同一个联通分量的边就可以返回了。
但对于第二种情况,我们需要找到那两条指向了同一个节点的边,然后尝试删除一条边,判断是否构成树。
cpp
class Solution {
public:
int find(int x)
{
if (tree[x] == x) return x;
return find(tree[x]);
}
void unite(int x, int y)
{
int fx = find(x), fy = find(y);
tree[fy] = fx;
}
bool isTree(vector<vector<int>>& edges, int ii)
{
const int n = edges.size();
tree.resize(n+1);
for (int i=1; i<=n; ++i) tree[i] = i;
for (int i=0; i<n; ++i)
{
if (i == ii) continue;
if (find(edges[i][0]) != find(edges[i][1]))
{
unite(edges[i][0], edges[i][1]);
}
else return false;
}
return true;
}
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
const int n = edges.size();
int inDgree[n+1]; //记录入度
int multi = 0;
for (int i=1; i<=n; ++i) inDgree[i]=0;
for (auto i:edges)
{
inDgree[i[1]]++;
if (inDgree[i[1]] == 2) multi = i[1]; // 找到入度为2的点,保证最多只有一个
}
if (multi != 0) // 如果存在入度为2的点
{
for (int i=n-1; i>=0; --i)
{
if (edges[i][1] == multi) // 从后往前尝试删除指向的边
{
if (isTree(edges, i)) // 如果后加入的边删除后构成树,那么就直接返回这条边即可
{
return edges[i];
}
else // 如果后加入的删除后无法构成树,那要删除的一定是先加入的那条边
{
while (--i)
{
if (edges[i][1] == multi)
{
return edges[i];
}
}
}
}
}
}
else // 没有入度为2的点,处理方法与昨天一致
{
tree.resize(n+1);
for (int i=1; i<=n; ++i) tree[i] = i;
for (int i=0; i<n; ++i)
{
if (find(edges[i][0]) != find(edges[i][1]))
{
unite(edges[i][0], edges[i][1]);
}
else return edges[i];
}
}
return edges[0];
}
private:
vector<int> tree;
};