https://leetcode.com/problems/redundant-connection/description/
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.
X. Union-Find
https://leetcode.com/problems/redundant-connection/discuss/123819/Union-Find-with-Explanations-(Java-Python)
https://leetcode.com/problems/redundant-connection/discuss/107984/10-line-Java-solution-Union-Find.
public int[] findRedundantConnection(int[][] edges) {
DSU dsu = new DSU(MAX_EDGE_VAL + 1);
for (int[] edge: edges) {
if (!dsu.union(edge[0], edge[1])) return edge;
}
throw new AssertionError();
}
}
class DSU {
int[] parent;
int[] rank;
public DSU(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) parent[i] = i;
rank = new int[size];
}
public int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
public boolean union(int x, int y) {
int xr = find(x), yr = find(y);
if (xr == yr) {
return false;
} else if (rank[xr] < rank[yr]) {
parent[xr] = yr;
} else if (rank[xr] > rank[yr]) {
parent[yr] = xr;
} else {
parent[yr] = xr;
rank[xr]++;
}
return true;
}
https://leetcode.com/problems/redundant-connection/discuss/107984/10-line-Java-solution-Union-Find.
X.
Set<Integer> seen = new HashSet();
int MAX_EDGE_VAL = 1000;
public int[] findRedundantConnection(int[][] edges) {
ArrayList<Integer>[] graph = new ArrayList[MAX_EDGE_VAL + 1];
for (int i = 0; i <= MAX_EDGE_VAL; i++) {
graph[i] = new ArrayList();
}
for (int[] edge: edges) {
seen.clear();
if (!graph[edge[0]].isEmpty() && !graph[edge[1]].isEmpty() &&
dfs(graph, edge[0], edge[1])) {
return edge;
}
graph[edge[0]].add(edge[1]);
graph[edge[1]].add(edge[0]);
}
throw new AssertionError();
}
public boolean dfs(ArrayList<Integer>[] graph, int source, int target) {
if (!seen.contains(source)) {
seen.add(source);
if (source == target) return true;
for (int nei: graph[source]) {
if (dfs(graph, nei, target)) return true;
}
}
return false;
}
X.
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:
X. Union-Find
https://leetcode.com/problems/redundant-connection/discuss/123819/Union-Find-with-Explanations-(Java-Python)
public int[] findRedundantConnection(int[][] edges) {
DisjointSet disjointSet = new DisjointSet(edges.length);
for (int[] edge : edges) {
if (!disjointSet.union(edge[0] - 1, edge[1] - 1)) return edge;
}
throw new IllegalArgumentException();
}
static class DisjointSet {
private int[] parent;
private byte[] rank;
public DisjointSet(int n) {
if (n < 0) throw new IllegalArgumentException();
parent = new int[n];
rank = new byte[n];
}
public int find(int x) {
if (parent[x] == 0) return x;
return parent[x] = find(parent[x]); // Path compression by halving.
}
// Return false if x, y are connected.
public boolean union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return false;
// Make root of smaller rank point to root of larger rank.
if (rank[rootX] < rank[rootY]) parent[rootX] = rootY;
else if (rank[rootX] > rank[rootY]) parent[rootY] = rootX;
else {
parent[rootX] = rootY;
rank[rootY]++;
}
return true;
}
}
https://leetcode.com/problems/redundant-connection/discuss/107984/10-line-Java-solution-Union-Find.
public int[] findRedundantConnection(int[][] edges) {
int[] parent = new int[2001];
for (int i = 0; i < parent.length; i++) parent[i] = i;
for (int[] edge: edges){
int f = edge[0], t = edge[1];
if (find(parent, f) == find(parent, t)) return edge;
else parent[find(parent, f)] = find(parent, t);
}
return new int[2];
}
private int find(int[] parent, int f) {
if (f != parent[f]) {
parent[f] = find(parent, parent[f]);
}
return parent[f];
}
If we are familiar with a Disjoint Set Union (DSU) data structure, we can use this in a straightforward manner to solve the problem: we simply find the first edge occurring in the graph that is already connected. The rest of this explanation will focus on the details of implementing DSU.
A DSU data structure can be used to maintain knowledge of the connected components of a graph, and query for them quickly. In particular, we would like to support two operations:
dsu.find(node x)
, which outputs a unique id so that two nodes have the same id if and only if they are in the same connected component, and:dsu.union(node x, node y)
, which draws an edge(x, y)
in the graph, connecting the components with idfind(x)
andfind(y)
together.
To achieve this, we keep track of
parent
, which remembers the id
of a smaller node in the same connected component. If the node is it's own parent, we call this the leader of that connected component.- Time Complexity: , where is the number of vertices (and also the number of edges) in the graph, and is the Inverse-Ackermann function. We make up to queries of
dsu.union
, which takes (amortized) time. Outside the scope of this article, it can be shown whydsu.union
has complexity, what the Inverse-Ackermann function is, and why is approximately . - Space Complexity: . The current construction of the graph (embedded in our
dsu
structure) has at most nodes.
public int[] findRedundantConnection(int[][] edges) {
DSU dsu = new DSU(MAX_EDGE_VAL + 1);
for (int[] edge: edges) {
if (!dsu.union(edge[0], edge[1])) return edge;
}
throw new AssertionError();
}
}
class DSU {
int[] parent;
int[] rank;
public DSU(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) parent[i] = i;
rank = new int[size];
}
public int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
public boolean union(int x, int y) {
int xr = find(x), yr = find(y);
if (xr == yr) {
return false;
} else if (rank[xr] < rank[yr]) {
parent[xr] = yr;
} else if (rank[xr] > rank[yr]) {
parent[yr] = xr;
} else {
parent[yr] = xr;
rank[xr]++;
}
return true;
}
https://leetcode.com/problems/redundant-connection/discuss/107984/10-line-Java-solution-Union-Find.
public int[] findRedundantConnection(int[][] edges) {
int[] parent = new int[2001];
for (int i = 0; i < parent.length; i++) parent[i] = i;
for (int[] edge: edges){
int f = edge[0], t = edge[1];
if (find(parent, f) == find(parent, t)) return edge;
else parent[find(parent, f)] = find(parent, t);
}
return new int[2];
}
private int find(int[] parent, int f) {
if (f != parent[f]) {
parent[f] = find(parent, parent[f]);
}
return parent[f];
}
X.
For each edge
(u, v)
, traverse the graph with a depth-first search to see if we can connect u
to v
. If we can, then it must be the duplicate edge.- Time Complexity: where is the number of vertices (and also the number of edges) in the graph. In the worst case, for every edge we include, we have to search every previously-occurring edge of the graph.
- Space Complexity: . The current construction of the graph has at most nodes.
Set<Integer> seen = new HashSet();
int MAX_EDGE_VAL = 1000;
public int[] findRedundantConnection(int[][] edges) {
ArrayList<Integer>[] graph = new ArrayList[MAX_EDGE_VAL + 1];
for (int i = 0; i <= MAX_EDGE_VAL; i++) {
graph[i] = new ArrayList();
}
for (int[] edge: edges) {
seen.clear();
if (!graph[edge[0]].isEmpty() && !graph[edge[1]].isEmpty() &&
dfs(graph, edge[0], edge[1])) {
return edge;
}
graph[edge[0]].add(edge[1]);
graph[edge[1]].add(edge[0]);
}
throw new AssertionError();
}
public boolean dfs(ArrayList<Integer>[] graph, int source, int target) {
if (!seen.contains(source)) {
seen.add(source);
if (source == target) return true;
for (int nei: graph[source]) {
if (dfs(graph, nei, target)) return true;
}
}
return false;
}
X.
public int[] findRedundantConnection(int[][] edges) {
Map<Integer, Set<Integer>> graph = buildGraph(edges);
LinkedHashSet<Integer> path = new LinkedHashSet<>();
Integer cycleNode = findCycle(graph, 1, -1, path);
System.out.println(path);
keepCycleNodesOnly(path, cycleNode);
System.out.println(path);
for (int i = edges.length - 1; i >= 0; i--) {
if (path.contains(edges[i][0]) && path.contains(edges[i][1])) {
return edges[i];
}
}
// should not happen
throw new AssertionError();
}
private void keepCycleNodesOnly(LinkedHashSet<Integer> path, Integer cycleNode) {
Iterator<Integer> it = path.iterator();
while (it.hasNext()) {
Integer curNode = it.next();
if (!Objects.equals(curNode, cycleNode)) {
it.remove();
} else {
break;
}
}
}
private Integer findCycle(Map<Integer, Set<Integer>> graph, int curNode, int prevNode,
LinkedHashSet<Integer> path) {
if (path.contains(curNode)) {
return curNode;
}
path.add(curNode);
if (graph.get(curNode) != null) {
for (Integer nb : graph.get(curNode)) {
// BUG: a-b b-a case
if (nb == prevNode)
continue;
Integer circleNode = findCycle(graph, nb, curNode, path);
if (circleNode != null) {
return circleNode;
}
}
}
path.remove(curNode);
return null;
}
private Map<Integer, Set<Integer>> buildGraph(int[][] edges) {
Map<Integer, Set<Integer>> graph = new HashMap<>();
for (int i = 0; i < edges.length; i++) {
graph.computeIfAbsent(edges[i][0], li -> new HashSet<>()).add(edges[i][1]);
graph.computeIfAbsent(edges[i][1], li -> new HashSet<>()).add(edges[i][0]);
}
return graph;
}