leetcode 310. Minimum Height Trees(最小高度树)

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.

Given a tree of n nodes labelled from 0 to n - 1, and an array of n - 1 edges where edges[i] = [ai, bi] indicates that there is an undirected edge between the two nodes ai and bi in the tree, you can choose any node of the tree as the root. When you select a node x as the root, the result tree has height h. Among all possible rooted trees, those with minimum height (i.e. min(h)) are called minimum height trees (MHTs).

Return a list of all MHTs’ root labels. You can return the answer in any order.

The height of a rooted tree is the number of edges on the longest downward path between the root and a leaf.
在这里插入图片描述
一个无向图表示的树,有n个节点(0到n-1),边edges = [ai, bi]表示第i条边的两个端点是ai, bi。
选任一节点作为root, 可得到一个树的高度。
返回可以得到最小高度的所有root

思路:
可以用拓扑排序,先介绍下拓扑排序。参考链接
在这里插入图片描述
这里同样选一个入度为0的node,一层一层删掉,最后剩<=2个node时结束,剩下的node就是我们需要的root

为什么要这样做?因为每次删掉的都是入度为0(最外层)的node,剩下的是处于最中间的node
为什么要选剩下的<=2个节点?剩一个节点时,它肯定是最中间的root。剩两个时,两个谁做root,高度都是一样的。当剩3个时,由于树中不存在环,肯定有一个高度是大于另外两个的。

具体实现步骤:

  1. 将输入的边edges构建成图,注意无向图要双向
  2. 找到入度为0(即仅有一个边)的node,放入queue
  3. 由于是无向图,需要从边的另一端点对应的连接node表中,删除当前node,删除完后,如果只剩下一个边,装入queue,作为下一轮删除对象
  4. 直到queue中只剩下<=2个节点,返回
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        List<Integer> roots = new ArrayList<>();
        if(n == 0 || edges == null) return roots;
        
        if(n == 1) {
            roots.add(0);
            return roots;
        }
        if(n == 2) {
            roots.add(0);
            roots.add(1);
            return roots;
        }
        
        //HashSet便于删除node
        List<Set<Integer>> graph = new ArrayList<>(n);
        
        for(int i = 0; i < n; i++) {
            graph.add(new HashSet<Integer>());
        }
        
        //无向图
        for(int[] edge : edges) {
            graph.get(edge[0]).add(edge[1]);
            graph.get(edge[1]).add(edge[0]);
        }
        
        for(int i = 0; i < n; i++) {
            if(graph.get(i).size() == 1) {
                roots.add(i);
            }
        }
        
        while(n > 2) {
            n -= roots.size();  //遍历下来roots里面的节点都要删除
            List<Integer> newRoots = new ArrayList<>(); //保存新的叶子节点
            for(Integer root : roots) {
                int node = graph.get(root).iterator().next();
                graph.get(node).remove(root);
                if(graph.get(node).size() == 1) newRoots.add(node);
            }
            roots = newRoots;
        }
        return roots;
        
    }

上面的方法有点耗时,主要是在于要从边的另一端点所对应的表中删除当前node。其实我们想要的只是看入度是否为0(是否只有一条边),即边的另一端点所对应的表的size是否为1。并不关心每个点具体和哪个点相连接。

可以建立一个数组,记录每个node有几条边,当只有一条边时加入queue,就不需要再反复地删除节点了。

以下是错误做法,因为如果没有新添加入度为0的节点,而queue中的节点仅剩两个的时候,就会跳出循环,queue中保存的之前的节点会处理不完

while(queue.size() > 2) {
            int deleteNode = queue.poll();
            //由于没有节点删除这一步了,要遍历整个连接点的表
            int deleteConnectNode = graph[deleteNode].get(0); //入度为0,肯定只连接一个点
            degree[deleteNode] --;
            degree[deleteConnectNode] --;
            if(degree[deleteConnectNode] == 1) queue.offer(deleteConnectNode);
        }

完整代码

    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        List<Integer> roots = new ArrayList<>();
        if(n == 0 || edges == null) return roots;
        
        if(n == 1) {
            roots.add(0);
            return roots;
        }
        if(n == 2) {
            roots.add(0);
            roots.add(1);
            return roots;
        }
        
        ArrayList<Integer>[] graph = new ArrayList[n];
        int[] degree = new int[n];
        Queue<Integer> queue = new LinkedList<Integer>();
        boolean[] removed = new boolean[n];
        
        for(int i = 0; i < n; i++) {
            graph[i] = new ArrayList<Integer>();
        }
        
        
        for(int[] edge : edges) {
            graph[edge[0]].add(edge[1]);
            graph[edge[1]].add(edge[0]);
            degree[edge[0]] ++;
            degree[edge[1]] ++;
        }
        
        for(int i = 0; i < n; i++) {
            if(degree[i] == 1) queue.offer(i); 
        }
        
        while(n > 2) {
            int size = queue.size();
            n -= size;
            while(size > 0) {
                int deleteNode = queue.poll();
                removed[deleteNode] = true;
                for(int deleteConnectNode : graph[deleteNode]) {
                    if(removed[deleteConnectNode]) continue;
                    degree[deleteConnectNode] --;
                    if(degree[deleteConnectNode] == 1) queue.offer(deleteConnectNode);
                }
                size --;
            }
        }
        
        roots.addAll(queue);
        return roots;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝羽飞鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值