# 685. Redundant Connection II
LeetCode Problem Link (opens new window)
In this problem, a rooted tree is a directed graph that satisfies the following conditions. The tree has only one root node, and all other nodes are successors of that root node. Every node except the root node has exactly one parent, while the root node has no parent.
Input a directed graph that consists of a tree with n nodes (node values are unique and range from 1 to n) and an additional directed edge. The additional edge consists of two different vertices within the range of 1 to n, and this additional edge is not part of the tree's existing edges.
The result graph is a 2D-array edges, where each element is a pair [ui, vi], indicating a directed edge from vertex ui to vertex vi, where ui is a parent of vi.
Return a removable edge such that the remaining graph is a rooted tree with n nodes. If there are multiple answers, return the answer that appears last in the given 2D-array.


Constraints:
- n == edges.length
- 3 <= n <= 1000
- edges[i].length == 2
- 1 <= ui, vi <= n
# Approach
First, focus on understanding this sentence in the problem: The graph consists of a tree with N nodes (nodes are uniquely numbered from 1 to N), and an additional edge. The endpoints of this additional edge are within the range of 1 to N, and this edge is not already present in the tree.
This implies that the graph in question was originally a tree, but with an additional edge added without increasing the number of nodes!
Also, if there are multiple solutions, return the one that appears last in the provided 2D-array. This implies that if there are two edges both of which can be removed to form a tree, the last one in sequence should be removed!
There are the following three cases. The first two involve a node with an in-degree of 2, as shown below:

There is only one node with an in-degree of 2. Why not consider the out-degree? Out-degrees are irrelevant because any parent node in a tree naturally has multiple children.
The third case is when there is no node with an in-degree of 2, which means there must be a directed cycle in the graph (note it's a directed cycle!)
Here's the representation:

First, calculate the in-degree of each node. Many often get confused when calculating the in-degree, unclear what edges[i][j] represents.
For example, if the given input is: edges = [[1,2],[1,3],[2,3]]
One might naturally think of the array as being: edges[1][2], edges[1][3], edges[2][3], but then wonder what the values of edges[1][2] actually mean. The more they think, the more confused they become.
In fact, edges = [[1,2],[1,3],[2,3]], means:
edges[0][0] = 1, edges[0][1] = 2,
edges[1][0] = 1, edges[1][1] = 3,
edges[2][0] = 2, edges[2][1] = 3.
2D arrays are something everyone learns, but when combined with graphs, it can be easy to mix up what are values and what are indices.
Once that's clear, how do we calculate the in-degree?
That is edges[i][1] denotes the node pointed to by an edge, indicating this node has an in-degree of one! (If you wanted to calculate the out-degree, it would be edges[i][0]).
The code to calculate in-degrees is as follows:
int inDegree[N] = {0}; // Record the in-degrees of nodes
n = edges.size(); // Number of edges
for (int i = 0; i < n; i++) {
inDegree[edges[i][1]]++; // Calculate in-degree
}
2
3
4
5
For the first two cases with a node having an in-degree of 2, it must be one of the two edges pointing to this node that needs to be removed. If removing one makes the graph a tree, then that edge is the answer. Also, we iterate from back to front so that if two edges can both be removed, we remove the last one.
Here's the code:
vector<int> vec; // Store edges ending in a node with in-degree 2 (there will be two if this case occurs)
// Identify edges corresponding to nodes with in-degree 2, note the iteration is reverse due to return order of result
for (int i = n - 1; i >= 0; i--) {
if (inDegree[edges[i][1]] == 2) {
vec.push_back(i);
}
}
// Handle cases 1 and 2
// If a node with in-degree 2 exists, one of its edges needs to be deleted to form a tree
if (vec.size() > 0) {
if (isTreeAfterRemoveEdge(edges, vec[0])) {
return edges[vec[0]];
} else {
return edges[vec[1]];
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In case three, when there are no nodes with an in-degree of 2, there must be a directed cycle in the graph. Identify the edge forming the cycle which should be removed.
The code below defines this function:
// Find and remove the edge such that the graph becomes a tree; the return value is the edge to be removed
vector<int> getRemoveEdge(const vector<vector<int>>& edges)
2
You will then understand that we need to implement two critical functions:
isTreeAfterRemoveEdge()which tests if removing one edge results in a tree.getRemoveEdgewhich assures there's a directed cycle in the graph and identifies the edge to be removed.
Now the union-find data structure comes into play. Why can union-find determine if a graph is a tree?
If the root of two vertices' edges can both be found in the union-find structure before adding any new edge, then this edge addition will ensure the graph is not a tree.
If you're not familiar with union-find, check Union-Find Theory Basics (opens new window).
Here is the full C++ code with ample comments:
class Solution {
private:
static const int N = 1010; // Node ranges between 3 and 1000 as given in the problem
int father[N];
int n; // Number of edges
// Initialize union-find structure
void init() {
for (int i = 1; i <= n; ++i) {
father[i] = i;
}
}
// Root finding process in union-find
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// Add edge v->u to the union-find structure
void join(int u, int v) {
u = find(u);
v = find(v);
if (u == v) return;
father[v] = u;
}
// Check if u and v have the same root
bool same(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// Find and return the removable edge in the directed graph to make it a tree
vector<int> getRemoveEdge(const vector<vector<int>>& edges) {
init(); // Initialize the union-find structure
for (int i = 0; i < n; i++) { // Iterate over all the edges
if (same(edges[i][0], edges[i][1])) { // A directed cycle is found, indicating the edge to be removed
return edges[i];
}
join(edges[i][0], edges[i][1]);
}
return {};
}
// Check if the graph becomes a tree after removing one edge
bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {
init(); // Initialize the union-find structure
for (int i = 0; i < n; i++) {
if (i == deleteEdge) continue;
if (same(edges[i][0], edges[i][1])) { // A directed cycle is found, not a tree
return false;
}
join(edges[i][0], edges[i][1]);
}
return true;
}
public:
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
int inDegree[N] = {0}; // Record the in-degrees of nodes
n = edges.size(); // Number of edges
for (int i = 0; i < n; i++) {
inDegree[edges[i][1]]++; // Calculate in-degree
}
vector<int> vec; // Record edges ending at a node with in-degree 2 (if exists, there are two)
// Identify edges corresponding to nodes with in-degree 2, note the iteration is reverse due to return order of result
for (int i = n - 1; i >= 0; i--) {
if (inDegree[edges[i][1]] == 2) {
vec.push_back(i);
}
}
// Handle cases 1 and 2
// If a node with in-degree 2 exists, one of its edges needs to be deleted to form a tree
if (vec.size() > 0) {
if (isTreeAfterRemoveEdge(edges, vec[0])) {
return edges[vec[0]];
} else {
return edges[vec[1]];
}
}
// Handle case 3
// If there are no nodes with in-degree 2, there must be a directed cycle, identify and return the edge forming it
return getRemoveEdge(edges);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# Other Language Versions
# Java
class Solution {
private static final int N = 1010; // Node ranges between 3 and 1000 as given in the problem
private int[] father;
public Solution() {
father = new int[N];
// Initialize union-find structure
for (int i = 0; i < N; ++i) {
father[i] = i;
}
}
// Root finding process in union-find
private int find(int u) {
if (u == father[u]) {
return u;
}
father[u] = find(father[u]);
return father[u];
}
// Add edge v->u to the union-find structure
private void join(int u, int v) {
u = find(u);
v = find(v);
if (u == v) return;
father[v] = u;
}
// Check if u and v have the same root, not utilized here
private Boolean same(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
/**
* Initialize the union-find structure.
*/
private void initFather() {
for (int i = 0; i < N; ++i) {
father[i] = i;
}
}
/**
* Identify and return the removable edge in the directed graph to make it a tree.
* @param edges
* @return the edge to be removed.
*/
private int[] getRemoveEdge(int[][] edges) {
initFather();
for (int i = 0; i < edges.length; i++) {
if (same(edges[i][0], edges[i][1])) { // A directed cycle is found, indicating the edge to be removed
return edges[i];
}
join(edges[i][0], edges[i][1]);
}
return null;
}
/**
* Check if the graph becomes a tree after removing one edge.
* @param edges
* @param deleteEdge the edge to be removed.
* @return true if it becomes a tree, false otherwise.
*/
private Boolean isTreeAfterRemoveEdge(int[][] edges, int deleteEdge) {
initFather();
for (int i = 0; i < edges.length; i++) {
if (i == deleteEdge) continue;
if (same(edges[i][0], edges[i][1])) { // A directed cycle is found, not a tree
return false;
}
join(edges[i][0], edges[i][1]);
}
return true;
}
public int[] findRedundantDirectedConnection(int[][] edges) {
int[] inDegree = new int[N];
for (int i = 0; i < edges.length; i++) {
// Compute in-degrees
inDegree[edges[i][1]] += 1;
}
// Identify edges corresponding to nodes with in-degree 2, note the iteration is reverse due to return order of result
ArrayList<Integer> twoDegree = new ArrayList<>();
for (int i = edges.length - 1; i >= 0; i--) {
if (inDegree[edges[i][1]] == 2) {
twoDegree.add(i);
}
}
// Handle cases 1 and 2
// If a node with in-degree 2 exists, one of its edges needs to be deleted to form a tree
if (!twoDegree.isEmpty()) {
if (isTreeAfterRemoveEdge(edges, twoDegree.get(0))) {
return edges[twoDegree.get(0)];
}
return edges[twoDegree.get(1)];
}
// Handle case 3
// If there are no nodes with in-degree 2, there must be a directed cycle, identify and return the edge forming it
return getRemoveEdge(edges);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# Python
class Solution:
def __init__(self):
self.n = 1010
self.father = [i for i in range(self.n)]
def find(self, u: int):
"""
Root finding process in union-find
"""
if u == self.father[u]:
return u
self.father[u] = self.find(self.father[u])
return self.father[u]
def join(self, u: int, v: int):
"""
Add edge v->u to the union-find structure
"""
u = self.find(u)
v = self.find(v)
if u == v: return
self.father[v] = u
pass
def same(self, u: int, v: int):
"""
Check if u and v have the same root
"""
u = self.find(u)
v = self.find(v)
return u == v
def init_father(self):
self.father = [i for i in range(self.n)]
pass
def getRemoveEdge(self, edges: List[List[int]]) -> List[int]:
"""
Identify and return the removable edge in the directed graph to make it a tree
"""
self.init_father()
for i in range(len(edges)):
if self.same(edges[i][0], edges[i][1]): # A directed cycle is found, indicating the edge to be removed
return edges[i]
self.join(edges[i][0], edges[i][1])
return []
def isTreeAfterRemoveEdge(self, edges: List[List[int]], deleteEdge: int) -> bool:
"""
Check if the graph becomes a tree after removing one edge
"""
self.init_father()
for i in range(len(edges)):
if i == deleteEdge: continue
if self.same(edges[i][0], edges[i][1]): # Directed cycle found, not a tree
return False
self.join(edges[i][0], edges[i][1])
return True
def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
inDegree = [0] * self.n
for i in range(len(edges)):
inDegree[edges[i][1]] += 1
# Identify edges corresponding to nodes with in-degree 2, reverse iteration for order of return
twoDegree = []
for i in range(len(edges))[::-1]:
if inDegree[edges[i][1]] == 2:
twoDegree.append(i)
# Handle cases 1 and 2
# If a node with in-degree 2 exists, one of its edges needs to be deleted to form a tree
if twoDegree:
if self.isTreeAfterRemoveEdge(edges, twoDegree[0]):
return edges[twoDegree[0]]
return edges[twoDegree[1]]
# Handle case 3
# If there are no nodes with in-degree 2, there must be a directed cycle, identify and return the edge forming it
return self.getRemoveEdge(edges)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# Go
// Global definitions
var (
n = 1010 // Nodes range from 3 to 1000
father = make([]int, n)
)
// Initialize union-find
func initialize() {
for i := 0; i < n; i++ {
father[i] = i
}
}
// Root finding process in union-find
func find(u int) int {
if u == father[u] {
return u
}
father[u] = find(father[u])
return father[u]
}
// Add edge v->u to the union-find structure
func join(u, v int) {
u = find(u)
v = find(v)
if u == v {
return
}
father[v] = u
}
// Check if u and v have the same root, not utilized here
func same(u, v int) bool {
u = find(u)
v = find(v)
return u == v
}
// getRemoveEdge Identify and return the removable edge in the directed graph to make it a tree
func getRemoveEdge(edges [][]int) []int {
initialize()
for i := 0; i < len(edges); i++ { // Iterate over all the edges
if same(edges[i][0], edges[i][1]) { // A directed cycle is found, indicating the edge to be removed
return edges[i]
}
join(edges[i][0], edges[i][1])
}
return []int{}
}
// isTreeAfterRemoveEdge Check if the graph becomes a tree after removing one edge
func isTreeAfterRemoveEdge(edges [][]int, deleteEdge int) bool {
initialize()
for i := 0; i < len(edges); i++ {
if i == deleteEdge {
continue
}
if same(edges[i][0], edges[i][1]) { // Directed cycle found, not a tree
return false
}
join(edges[i][0], edges[i][1])
}
return true
}
func findRedundantDirectedConnection(edges [][]int) []int {
inDegree := make([]int, len(father))
for i := 0; i < len(edges); i++ {
// Compute in-degrees
inDegree[edges[i][1]] += 1
}
// Store edges whose endpoints have in-degree 2 (only two in this case)
// Identify edges corresponding to nodes with in-degree 2, note the iteration is reverse due to return order of result
twoDegree := []int{}
for i := len(edges) - 1; i >= 0; i-- {
if inDegree[edges[i][1]] == 2 {
twoDegree = append(twoDegree, i)
}
}
// Handle cases 1 and 2
// If a node with in-degree 2 exists, one of its edges needs to be deleted to form a tree
if len(twoDegree) > 0 {
if isTreeAfterRemoveEdge(edges, twoDegree[0]) {
return edges[twoDegree[0]]
}
return edges[twoDegree[1]]
}
// Handle case 3
// If there are no nodes with in-degree 2, there must be a directed cycle, identify and return the edge forming it
return getRemoveEdge(edges)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# JavaScript
const N = 1010; // Nodes range from 3 to 1000
const father = new Array(N);
let n; // Number of edges
// Root finding process in union-find
const find = u => {
return u == father[u] ? u : father[u] = find(father[u]);
};
// Add edge v->u to the union-find structure
const join = (u, v) => {
u = find(u);
v = find(v);
if(u == v) return;
father[v] = u;
};
// Check if u and v have the same root
const same = (u, v) => {
u = find(u);
v = find(v);
return u == v;
};
// Identify and return the removable edge in the directed graph to make it a tree
const getRemoveEdge = edges => {
// Initialize union-find
for (let i = 1; i <= n; i++) {
father[i] = i;
}
for (let i = 0; i < n; i++) { // Iterate over all edges
if (same(edges[i][0], edges[i][1])) { // A directed cycle is found, indicating the edge to be removed
return edges[i];
}
join(edges[i][0], edges[i][1]);
}
return [];
}
// Check if the graph becomes a tree after removing one edge
const isTreeAfterRemoveEdge = (edges, deleteEdge) => {
// Initialize union-find
for (let i = 1; i <= n; i++) {
father[i] = i;
}
for (let i = 0; i < n; i++) {
if (i == deleteEdge) continue;
if (same(edges[i][0], edges[i][1])) { // Directed cycle found, not a tree
return false;
}
join(edges[i][0], edges[i][1]);
}
return true;
}
/**
* @param {number[][]} edges
* @return {number[]}
*/
var findRedundantDirectedConnection = function(edges) {
n = edges.length; // Number of edges
const inDegree = new Array(n + 1).fill(0); // Record node in-degrees
for (let i = 0; i < n; i++) {
inDegree[edges[i][1]]++; // Compute in-degrees
}
let vec = []; // Store edges ending in nodes with in-degree 2 (there will be two if this case exists)
// Identify edges corresponding to nodes with in-degree 2, note the iteration is reverse due to return order of result
for (let i = n - 1; i >= 0; i--) {
if (inDegree[edges[i][1]] == 2) {
vec.push(i);
}
}
// Handle cases 1 and 2
// If a node with in-degree 2 exists, one of its edges needs to be deleted to form a tree
if (vec.length > 0) {
if (isTreeAfterRemoveEdge(edges, vec[0])) {
return edges[vec[0]];
} else {
return edges[vec[1]];
}
}
// Handle case 3
// If there are no nodes with in-degree 2, there should be a directed cycle; identify and return the edge forming it
return getRemoveEdge(edges);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85