310. Minimum Height Trees

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 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 = 4edges = [[1, 0], [1, 2], [1, 3]]

        0
        |
        1
       / \
      2   3

return [1]

Example 2:

Given n = 6edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]

     0  1  2
      \ | /
        3
        |
        4
        |
        5

return [3, 4]

Show Hint 

    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.



    思路:又遇上了最痛苦的topological sort,赶紧补一下,http://blog.csdn.net/dm_vincent/article/details/7714519

    其实还是比较简单的,就想着 “学某门课要先修某门课” 这个实际的例子,把indegree为0的先拿出来,也就是不需要任何先修课的课程可以先学,然后更新其他节点的indegree,因为已经修了一门了嘛,说不定可以修其他以此为基础的课程呢!

    拓扑排序有2中实现方式:

    (1)BFS(就是上面描述的)

    (2)DFS (就是Coursera上algorithm课程老师描述的)


    类似的题目:

    https://leetcode.com/problems/course-schedule/?tab=Description

    https://leetcode.com/problems/course-schedule-ii/?tab=Description(只记录degree计数即可)


    这里当做无向图来对待:

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.List;
    
    /*
     * 找到最长的那条path,中间的1个或者2个就是要找的
     * 参考拓扑排序的BFS实现
     * 如果有1条最长的path,那不断减掉degree为1的,最后剩下的就是
     * 如果有2条最长的path,那他们必然相交于中点,如果不是,就必然可以找到更长的path,相交于中点的话,不断减掉degree为1的仍然是可行的
     */
    public class Solution {
        public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        	
        	if(n == 1)	return Collections.singletonList(0);
        	
        	// 构建degree集合,因为要最后的结果,所以不能简单用一个数组对degree计数
        	List<HashSet<Integer>> degree = new ArrayList<HashSet<Integer>>();
        	for(int i=0; i<n; i++)	degree.add(new HashSet<Integer>());
        	
        	for(int[] edge : edges) {
        		degree.get(edge[0]).add(edge[1]);
        		degree.get(edge[1]).add(edge[0]);
        	}
        	
        	
        	// 不断删除degree为0的节点,再更新剩下的节点的degree,所以用一个数组保存当前degree为0的节点
        	List<Integer> leaves = new ArrayList<Integer>();
        	for(int i=0; i<n; i++) 
        		if(degree.get(i).size() == 1)
        			leaves.add(i);
        	
        	// 一直往中点收缩,剩下个1就是1个,剩下2个就是2个
        	while(n > 2) {
        		
        		n = n - leaves.size();
        		List<Integer> newLeaves = new ArrayList<Integer>();
        		
        		// 更新degree
        		for(int leaf : leaves) {
        			for(int j : degree.get(leaf)) {
        				degree.get(j).remove(leaf);
        				
        				// 不可能出现degree为0,
        				if(degree.get(j).size() == 1)
        					newLeaves.add(j);
        			}
        		}
        		
        		leaves = newLeaves;
        	}
        	
        	return leaves;
        }
    }


    二刷:

    看了一下第一次刷写的博客,感觉完全不一样,还是要多刷,不然都找不到题目的感觉

    这次刷因为有一刷的概念,直接想到了从两边收索找中点的思路,写了以下:

    package l310;
    
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    /*
     * last case TLE
     */
    public class ArrayBFS {
        public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        	int[] degree = new int[n];
        	boolean[][] adj = new boolean[n][n];
        	for(int[] edge : edges) {
        		degree[edge[0]] ++;
        		degree[edge[1]] ++;
        		adj[edge[0]][edge[1]] = true;
        		adj[edge[1]][edge[0]] = true;
        	}
        	
        	int cnt = 0;
        	while(cnt < n-2) {
        		Set<Integer> s = new HashSet<Integer>();
        		for(int i=0; i<n; i++)
            		if(degree[i] == 1) {
            			degree[i] = -1;
            			s.add(i);
            		}
        		
        		for(int i : s)
        			for(int j=0; j<n; j++)	
        				if(adj[i][j])	degree[j] --;
        		
        		cnt += s.size();
        	}
        	
        	List<Integer> ret = new ArrayList<Integer>();
        	for(int i=0; i<n; i++)
        		if(degree[i] >= 0)	ret.add(i);
        	return ret;
        }
    }

    猜想可能是因为是稀疏矩阵,用连接表可能会快一点

    package l310;
    
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    /*
     * last case TLE
     * so we use Map or List, notice we will always have 0 .. n-1, so we just use List
     * 还可以优化,求degree为1的节点的时候不需要遍历,只要每当有degree为1就加到一个Set里面(这好像也是拓扑排序的优化技巧)
     */
    public class Solution {
        public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        	int[] degree = new int[n];
        	List<List<Integer>> adj = new ArrayList<List<Integer>>();
        	for(int i=0; i<n; i++)	adj.add(new ArrayList<Integer>());
        	for(int[] edge : edges) {
        		degree[edge[0]] ++;
        		degree[edge[1]] ++;
        		adj.get(edge[0]).add(edge[1]);
        		adj.get(edge[1]).add(edge[0]);
        	}
        	
        	int cnt = 0;
        	while(cnt < n-2) {
        		Set<Integer> s = new HashSet<Integer>();
        		for(int i=0; i<n; i++)
            		if(degree[i] == 1) {
            			degree[i] = -1;
            			s.add(i);
            		}
        		
        		for(int i : s)
        			for(int j : adj.get(i))	
        				degree[j] --;
        		
        		cnt += s.size();
        	}
        	
        	List<Integer> ret = new ArrayList<Integer>();
        	for(int i=0; i<n; i++)
        		if(degree[i] >= 0)	ret.add(i);
        	return ret;
        }
    }


    看到有人说暴力也可以AC,就试了一下遍历每个节点当root求树高,再取最小值

    package l310;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /*
     * TLE
     */
    public class BruteForce {
        public List<Integer> findMinHeightTrees(int n, int[][] edges) {
            Map<Integer, List<Integer>> m = new HashMap<Integer, List<Integer>>();
            for(int[] edge : edges) {
            	if(!m.containsKey(edge[0]))	m.put(edge[0], new ArrayList<Integer>());
            	m.get(edge[0]).add(edge[1]);
            	if(!m.containsKey(edge[1]))	m.put(edge[1], new ArrayList<Integer>());
            	m.get(edge[1]).add(edge[0]);
            }
            
            int min = Integer.MAX_VALUE;
            List<Integer> ret = new ArrayList<Integer>();
            ret.add(0);
            
            for(int root : m.keySet()) {
            	boolean[] marked = new boolean[n];
            	marked[root] = true;
            	int h = getHeight(m, root, marked);
            	if(h < min)	{
            		min = h;
            		ret.clear();
            		ret.add(root);
            	} else if(h == min) {
            		ret.add(root);
            	}
            }
            
            return ret;
        }
    
    	private int getHeight(Map<Integer, List<Integer>> m, int root, boolean[] marked) {
    		int ret = 0;
    		for(int next : m.get(root))
    			if(!marked[next]) {
    				marked[next] = true;
    				ret = Math.max(ret, 1 + getHeight(m, next, marked));
    				marked[next] = false;
    			}
    		return ret;
    	}
    }

    也试了一下Floyd算法

    package l310;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    /*
     * Floyd
     * 某一行最大也不超过最大的一半就是可能的root
     */
    public class Floyd {
        public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        	
        	// Floyd
        	int[][] adj = new int[n][n];
        	for(int[] a : adj)	Arrays.fill(a, Integer.MAX_VALUE);
            for(int[] edge : edges) {
            	adj[edge[0]][edge[1]] = 1;
            	adj[edge[1]][edge[0]] = 1;
            }
            for(int k=0; k<n; k++)
            	for(int i=0; i<n; i++)
            		for(int j=0; j<n; j++)
            			if(adj[i][k]!=Integer.MAX_VALUE && adj[k][j]!=Integer.MAX_VALUE 
            					&& i!=j && adj[i][k] + adj[k][j] < adj[i][j]) 
            				adj[i][j] = adj[i][k] + adj[k][j];
            
            // 
            int[] max = new int[n];
            for(int i=0; i<n; i++)	
            	for(int j=0; j<n; j++)
            		if(adj[i][j] != Integer.MAX_VALUE)
            			max[i] = Math.max(max[i], adj[i][j]);
            int allMax = 0;
            for(int a : max)	
            	if(a != Integer.MAX_VALUE)	allMax = Math.max(allMax, a);
            int validMax = (allMax+1)/2;
            
            List<Integer> ret = new ArrayList<Integer>();
            for(int i=0; i<n; i++)
            	if(max[i] <= validMax)	ret.add(i);
            return ret;
    	}
    }


    但是这些暴力解答都没有AC



    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值