public List<Integer> findMinHeightTrees(int n, int[][] edges)
    {
        if (n == 1) {
            return Collections.singletonList(0);
        }
        
        @SuppressWarnings("unchecked")
        Set<Integer>[] adjacencyTable = ((Set<Integer>[])new HashSet[n]);

        for (int i = 0; i < n; ++i) {
            adjacencyTable[i] = new HashSet<>();
        }
        
        // Build the adjacency table for the graph
        for (int[] edge : edges)
        {
            int start = edge[0];
            int end = edge[1];
            adjacencyTable[start].add(end);
            adjacencyTable[end].add(start);
        }
        
        // Initial set of leaves
        List<Integer> leaves = new ArrayList<>();
        for (int i = 0; i < n; ++i)
        {
            if (adjacencyTable[i].size() == 1) {
                leaves.add(i);
            }
        }
        
        List<Integer> result = leaves;
        List<Integer> newLeaves = new ArrayList<>();
        
        // Note that a graph can have at most 2 MHTs
        while (n > 2)
        {
            n -= leaves.size();
            newLeaves.clear();
            // Remove the old leaves from their parents adjacency set
            // and build a new list of leaves
            // (don't worry about removing the leaf -> parent edge)
            for (Integer leaf : leaves) {
                int parent = adjacencyTable[leaf].iterator().next();
                Set<Integer> connected = adjacencyTable[parent];
                connected.remove(leaf);
                if (connected.size() == 1) {
                    newLeaves.add(parent);
                }
            }
            
            // Switcheroo for recycling the ArrayLists
            result = newLeaves;
            newLeaves = leaves;
            leaves = result;
        }
        
        return result;
    }