package com.leetcode.graphs; import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /** * Level: Medium * Problem Link: https://leetcode.com/problems/graph-valid-tree/ * Problem Description: * Given n nodes labeled from 0 to n-1 and a list of undirected edges (each edge is a pair of nodes), write a function * to check whether these edges make up a valid tree. *

* Example 1: * Input: n = 5, and edges = [[0,1], [0,2], [0,3], [1,4]] * Output: true *

* Example 2: * Input: n = 5, and edges = [[0,1], [1,2], [2,3], [1,3], [1,4]] * Output: false *

* Note: you can assume that no duplicate edges will appear in edges. Since all edges are undirected, [0,1] is the * same as [1,0] and thus will not appear together in edges. * * @author rampatra * @since 2019-08-05 */ public class GraphValidTree { /** * * @param n * @param edges * @return */ public static boolean isValidTree(int n, int[][] edges) { List> adjacencyList = new ArrayList<>(n); for (int i = 0; i < n; i++) { adjacencyList.add(new ArrayList<>()); } for (int i = 0; i < edges.length; i++) { adjacencyList.get(edges[i][0]).add(edges[i][1]); } boolean[] visited = new boolean[n]; if (hasCycle(adjacencyList, 0, -1, visited)) { return false; } for (int i = 0; i < n; i++) { if (!visited[i]) { return false; } } return true; } private static boolean hasCycle(List> adjacencyList, int node1, int exclude, boolean[] visited) { visited[node1] = true; for (int i = 0; i < adjacencyList.get(node1).size(); i++) { int node2 = adjacencyList.get(node1).get(i); if ((visited[node2] && exclude != node2) || (!visited[node2] && hasCycle(adjacencyList, node2, node1, visited))) { return true; } } return false; } /** * Union-find algorithm: We keep all connected nodes in one set in the union operation and in find operation we * check whether two nodes belong to the same set. If yes then there's a cycle and if not then no cycle. * * Good articles on union-find: * - https://www.hackerearth.com/practice/notes/disjoint-set-union-union-find/ * - https://www.youtube.com/watch?v=wU6udHRIkcc * * @param n * @param edges * @return */ public static boolean isValidTreeUsingUnionFind(int n, int[][] edges) { int[] roots = new int[n]; for (int i = 0; i < n; i++) { roots[i] = i; } for (int i = 0; i < edges.length; i++) { // find operation if (roots[edges[i][0]] == roots[edges[i][1]]) { return false; } // union operation roots[edges[i][1]] = findRoot(roots, roots[edges[i][0]]); // note: we can optimize this even further by // considering size of each side and then join the side with smaller size to the one with a larger size (weighted union). // We can use another array called size to keep count of the size or we can use the same root array with // negative values, i.e, negative resembles that the node is pointing to itself and the number will represent // the size. For example, roots = [-2, -1, -1, 0] means that node 3 is pointing to node 0 and node 0 is pointing // to itself and is has 2 nodes under it including itself. } return edges.length == n - 1; } private static int findRoot(int[] roots, int node) { while (roots[node] != node) { node = roots[node]; } return node; } public static void main(String[] args) { assertTrue(isValidTree(5, new int[][]{{0, 1}, {0, 2}, {0, 3}, {1, 4}})); assertFalse(isValidTree(5, new int[][]{{0, 1}, {1, 2}, {2, 3}, {1, 3}, {1, 4}})); assertFalse(isValidTree(3, new int[][]{{0, 1}, {1, 2}, {2, 0}})); assertTrue(isValidTreeUsingUnionFind(5, new int[][]{{0, 1}, {0, 2}, {0, 3}, {1, 4}})); assertFalse(isValidTreeUsingUnionFind(5, new int[][]{{0, 1}, {1, 2}, {2, 3}, {1, 3}, {1, 4}})); assertFalse(isValidTreeUsingUnionFind(3, new int[][]{{0, 1}, {1, 2}, {2, 0}})); } }