For a undirected graph with tree characteristics, we can choose any node as the root. The result graph is then a rooted tree. Among all possible rooted trees, those with minimum height are called minimum height trees (MHTs). Given such a graph, write a function to find all the MHTs and return a list of their root labels.
Format
The graph contains
The graph contains
n
nodes which are labeled from 0
to n - 1
. You will be given the number n
and a list of undirected edges
(each edge is a pair of labels).
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
.
Example 1:
Given
n = 4
, edges = [[1, 0], [1, 2], [1, 3]]
0 | 1 / \ 2 3
return
[1]
Example 2:
Given
n = 6
, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]
0 1 2 \ | / 3 | 4 | 5
return
Show Hint [3, 4]
Note:
(1) According to the definition of tree on Wikipedia: “a tree is an undirected graph in which any two vertices are connected by exactly one path. In other words, any connected graph without simple cycles is a tree.”
(2) The height of a rooted tree is the number of edges on the longest downward path between the root and a leaf.
A brute-force O(n^2) solution:A brute-force solution is we can construct the graph first, then for each vertex as a root of the tree, we calculate the height, and then compare the height with the minimum height we got so far. Update the minimum if necessary. Since getting the height of a tree takes O(n) time, and we need to traverse each vertex of the graph, the total time complexity is O(n^2).
Code (Java):
public class Solution { public List<Integer> findMinHeightTrees(int n, int[][] edges) { List<Integer> result = new ArrayList<>(); if (n <= 0 || edges == null || edges.length == 0) { return result; } // Step 1: construct the adjList Map<Integer, List<Integer>> adjList = new HashMap<>(); for (int[] edge : edges) { // add forward edge int from = edge[0]; int to = edge[1]; if (!adjList.containsKey(from)) { List<Integer> neighbors = new ArrayList<>(); neighbors.add(to); adjList.put(from, neighbors); } else { List<Integer> neighbors = adjList.get(from); neighbors.add(to); adjList.put(from, neighbors); } // Add the reverse edge if (!adjList.containsKey(to)) { List<Integer> neighbors = new ArrayList<>(); neighbors.add(from); adjList.put(to, neighbors); } else { List<Integer> neighbors = adjList.get(to); neighbors.add(from); adjList.put(to, neighbors); } } // Step 2: iterate each vertex as the root and get the height boolean[] visited = new boolean[n]; int minHeight = Integer.MAX_VALUE; for (int i = 0; i < n; i++) { int height = getHeightOfTree(i, adjList, visited); if (height < minHeight) { result.clear(); result.add(i); minHeight = height; } else if (height == minHeight) { result.add(i); } } return result; } private int getHeightOfTree(int root, Map<Integer, List<Integer>> adjList, boolean[] visited) { List<Integer> neighbors = adjList.get(root); visited[root] = true; int maxHeight = 0; for (Integer neighbor : neighbors) { if (!visited[neighbor]) { maxHeight = Math.max(maxHeight, getHeightOfTree(neighbor, adjList, visited)); } } visited[root] = false; return maxHeight + 1; } }
First let's review some statement for tree in graph theory:
(1) A tree is an undirected graph in which any two vertices are connected by exactly one path.(2) Any connected graph who hasn
nodes withn-1
edges is a tree.(3) The degree of a vertex of a graph is the number of edges incident to the vertex.(4) A leaf is a vertex of degree 1. An internal vertex is a vertex of degree at least 2.(5) A path graph is a tree with two or more vertices that is not branched at all.(6) A tree is called a rooted tree if one vertex has been designated the root.(7) The height of a rooted tree is the number of edges on the longest downward path between root and a leaf.
OK. Let's stop here and look at our problem.
Our problem want us to find the minimum height trees and return their root labels. First we can think about a simple case -- a path graph.
For a path graph of
n
nodes, find the minimum height trees is trivial. Just designate the middle point(s) as roots.
Despite its triviality, let design a algorithm to find them.
Suppose we don't know
n
, nor do we have random access of the nodes. We have to traversal. It is very easy to get the idea of two pointers. One from each end and move at the same speed. When they meet or they are one step away, (depends on the parity of n
), we have the roots we want.
This gives us a lot of useful ideas to crack our real problem.
For a tree we can do some thing similar. We start from every end, by end we mean vertex of degree 1 (aka leaves). We let the pointers move the same speed. When two pointers meet, we keep only one of them, until the last two pointers meet or one step away we then find the roots.
It is easy to see that the last two pointers are from the two ends of the longest path in the graph.
The actual implementation is similar to the BFS topological sort. Remove the leaves, update the degrees of inner vertexes. Then remove the new leaves. Doing so level by level until there are 2 or 1 nodes left. What's left is our answer!
The time complexity and space complexity are both O(n).
Note that for a tree we always have
Code (Java):V = n
, E = n-1
.public class Solution { public List<Integer> findMinHeightTrees(int n, int[][] edges) { List<Integer> result = new ArrayList<>(); if (n <= 0) { return result; } // Corner case: there is a single node and no edge at all if (n == 1 && edges.length == 0) { result.add(0); return result; } // Step 1: construct the graph List<Set<Integer>> adjList = new ArrayList<>(); for (int i = 0; i < n; i++) { adjList.add(new HashSet<>()); } for (int[] edge : edges) { int from = edge[0]; int to = edge[1]; adjList.get(from).add(to); adjList.get(to).add(from); } // Remove leaf nodes List<Integer> leaves = new ArrayList<>(); for (int i = 0; i < n; i++) { if (adjList.get(i).size() == 1) { leaves.add(i); } } while (n > 2) { // identify and remove all leaf nodes n -= leaves.size(); List<Integer> newLeaves = new ArrayList<>(); for (int leaf : leaves) { int neighbor = adjList.get(leaf).iterator().next(); adjList.get(neighbor).remove(leaf); if (adjList.get(neighbor).size() == 1) { newLeaves.add(neighbor); } } leaves = newLeaves; } return leaves; } }
Summary:
1. Note in the implementation, we use List<Set<Integer>> to represent a adj list. That is because the vertex Id ranges from 0 to n - 1, we can use the list index to represent the vertex Id.
2. In the implementation, we don't really delete the leaf nodes, which will result in an O(n) time since the adjList is a list. Instead, we find the leaf nodes in each iteration and remove it in the neighbor list. Then we find out the new leaf nodes.
hello.what would be the time complexity of the second solution?
ReplyDeleteIs the worst case still o(N^2) for linear graph?