第一次写博客,一直以来图题都是弱点,这几天专攻了一下,做的时候以为这道题是道中等难度的题,做了两个多小时,很是郁闷。做完之后才发现是Hard题,又很是兴奋,又看了看好像也不知道该怎么优化了。
话不多说,上题:
685. Redundant Connection IIIn this problem, a rooted tree is a directed graph such that, there is exactly one node (the root) for which all other nodes are descendants of this node, plus every node has exactly one parent, except for the root node which has no parents.
The given input is a directed graph that started as a rooted tree with n
nodes (with distinct values from 1
to n
), with one additional directed 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 [ui, vi]
that represents a directed edge connecting nodes ui
and vi
, where ui
is a parent of child vi
.
Return an edge that can be removed so that the resulting graph is a rooted tree of n
nodes. If there are multiple answers, return the answer that occurs last in the given 2D-array.
Example 1:
Input: edges = [[1,2],[1,3],[2,3]] Output: [2,3]
Example 2:
Input: edges = [[1,2],[2,3],[3,4],[4,1],[1,5]] Output: [4,1]
Constraints:
n == edges.length
3 <= n <= 1000
edges[i].length == 2
1 <= ui, vi <= n
解释:拿到题时开始没什么思路,一遍一遍读题,因为每个节点只有一个父结点,所以图可以看成一棵树,题目要求输出最后一条冗余的边,什么是冗余的边呢?
1,要么是同一节点有两个父结点时,有一条冗余边
2,要么是形成了回路,也就是环,肯定有条冗余边
因为题中说every node has exactly one parent, 因此想到可以用并查集来做,parent数组中parent[i]记录它的直接父结点。题中又说如果有多个答案的话返回最后的那个(暗示可以流水操作,不需要建立链接表)。有了大体思路,可以开始做题(列提纲)拉!
用vector<int>变量circle记录组成环形的最后一条边,first变量记录在一个节点有两个parent的情况下第一个parent,doubled记录第二个parent。
1,初始化parent[i] = i,每个节点都是自己的父结点,节点间互不联系
2,从头往后遍历输入数组的边,加入边时有几种情况:
1)加入当前边后,图中组成了一个环,此时当前边不加入并查集,避免后续查询父结点时进入死循环。但当前边记录在circle变量中。
a) 如果此时已经出现一个节点有两个父结点的情况,则返回第一个父结点的边(因为在记录并查集时,第二个父结点并不记录进去,因此计算是否有环是使用第一个父结点计算的,因此第一个父结点是冗余边,例{{2,1},{3,1},{4,2},{1,4}},输出为{2,1})。
b)如果此时未出现两个父结点的情况,不能判断哪条边是冗余边,将最后组成环的边记录在circle中。以便最后输出时输出最后出现的一个。
2)加入当前边后,当前边指向的节点已经有了父结点,即当前边构成了doubled节点。此时当前边不加入并查集(也可以加入并查集,但在circle计算时返回的值应相应的改为doubled而不是first了,其它地方也需要相应的调整),但记录在doubled变量里,同时将{parent[e[1]], e[1]}记录在first里,因为parent[e[1]]记录的是上一个父结点。
a) 如果此时已出现过环,则必为当前指向节点的前父结点和当前指向节点为冗余边,因为题目要求只有一条冗余边,而first, 与doubled出现两个父结点必有一条冗余边,而之前的环肯定是通过first计算出来的,因此first是冗余边。此时可以直接返回first。
b)如果没出现过环,只将first和doubled记录即可
3)如果当前边加入后即无增加的环也无增加的double,只需将parent数组更新即可。parent[e[1]] = e[0],记录其直接父结点。
4)有可能存在一条边加入后同时构成环和doubled,此时将边记录在circle中,因此最后返回时先返回circle,再返回doubled。如果只有circle或只有doubled的情况,则circle和doubled记录的都是最后出现的一条边,直接返回即可。
代码:
1 class Solution { 2 public: 3 int find_parent(vector<int>& parent, int p){ 4 while(parent[p] != p) 5 p = parent[p]; 6 return p; 7 } 8 vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) { 9 int n = edges.size(); 10 vector<int> parent(n+1, 0); 11 for(int i=0; i<parent.size(); i++) parent[i] = i; 12 vector<int> circle, first, doubled; 13 for(auto e : edges){ 14 int x = find_parent(parent, e[0]); 15 int y = find_parent(parent, e[1]); 16 if(x == y) { 17 if(doubled.size() > 0) 18 return first; 19 circle = e; 20 } else if(y != e[1]) { 21 first = {parent[e[1]], e[1]}; 22 doubled = e; 23 if(circle.size() > 0) 24 return first; 25 } else parent[e[1]] = e[0]; 26 } 27 if(circle.size() > 0) return circle; 28 else return doubled; 29 } 30 };
运行之后time打败了99.75,很是兴奋,就写它当我的第一篇博客吧,欢迎指正交流。