LeetCode 310. Minimum Height Trees 解法

LeetCode 310. Minimum Height Trees 解法

最简单的解法就是DFS遍历,即从每个节点出发,计算到达末端节点的最大路径长度,那么此路径长度最小的就是最矮树的根(可能有多个)。但是这个方法会超时,代码如下(Java):

class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        if (n < 0) return new ArrayList<Integer>(0);
        if (n == 1) return Arrays.asList(0);

        // construct adjacent list
        List<Integer>[] adj = (List<Integer>[]) new ArrayList[n];
        for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();
        for (int[] edge : edges) {
            adj[edge[0]].add(edge[1]);
            adj[edge[1]].add(edge[0]);
        }

        int[] height = new int[n];
        boolean[] visited = new boolean[n];
        for (int i = 0; i < n; i++) {
            // DFS from node i and record maxHeight
            Arrays.fill(visited, false);
            int maxHeight = 0; 
            Stack<int[]> s = new Stack<>();
            s.push(new int[] {i, 0});
            while (!s.isEmpty()) {
                int[] curr = s.pop();
                visited[curr[0]] = true;
                for (int next : adj[curr[0]]) {
                    if (!visited[next]) {
                        s.push(new int[] {next, curr[1] + 1});
                    }
                }
                maxHeight = Math.max(maxHeight, curr[1]);
            }
            height[i] = maxHeight;
        }

        int minHeight = height[0];
        for (int h : height) minHeight = Math.min(minHeight, h);

        ArrayList<Integer> result = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (minHeight == height[i]) {
                result.add(i);
            }
        }
        return result;
    }
}

看来需要想一种聪明的办法。我们可以这样直观地想:每次删除这颗最矮树的叶子节点,删到最后,剩下的1个或者2个节点就是最矮树的根。我们可以确定最矮树的叶子节点就是所有度为1的叶子节点,也就是说只需要每次删除所有度为1的节点就可以了。接下来我们严格证明这样做是可行的:

Lemma: 设V为有向无环图G(N; E)中最矮树的根,且size(N) >= 3,则删除所有度为1的节点后,V仍然是G中最矮树的根。
Proof:
首先,V的度必不为1。若V的度为1,则它的相邻节点W的度一定大于1(否则G中就只有2个节点),且树W的高度比树V小1(画图看看就知道了),矛盾。因此删除的节点中不包括V。
在删除所有度为1的系欸但后,则删除了树V所有的叶子,使得它高度减小1。假设删除后最矮树的根(之一)为W,且W和V不同,则有:
height after ( W ) < height before ( V ) − 1 \text{height}_{\text{after}}(W) < \text{height}_{\text{before}}(V) - 1 heightafter(W)<heightbefore(V)1

height after ( W ) + 1 < height before ( V ) \text{height}_{\text{after}}(W) + 1 < \text{height}_{\text{before}}(V) heightafter(W)+1<heightbefore(V)

height before ( W ) < height before ( V ) \text{height}_{\text{before}}(W) < \text{height}_{\text{before}}(V) heightbefore(W)<heightbefore(V)

与删除前(before)树V的高度最小矛盾,求证得证。

好,算法题成功地被做成了数学题,接下来可以写代码了,代码的思路就是从叶子节点开始BFS(新的叶子一定是通过删除当前叶子产生的,因此只需检查与当前叶子的邻居的度),直到整个图遍历完了,就把最后一次删掉的节点返回。这段代码挺难写的,忽略一些特殊情况容易出bug。

class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        if (n <= 0) return new ArrayList<Integer>(0);
        if (n == 1) return Collections.singletonList(0);

        // construct adjacent list and compute degree
        List<Integer>[] adj = (List<Integer>[]) new ArrayList[n];
        int[] degree = new int[n];
        Arrays.fill(degree, 0);
        for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();
        for (int[] edge : edges) {
            adj[edge[0]].add(edge[1]);
            adj[edge[1]].add(edge[0]);
            degree[edge[0]]++;
            degree[edge[1]]++;
        }

        // just bfs, we need to know what
        ArrayList<Integer> lastDelete = new ArrayList<>(); // last leaves
        Queue<Integer> leaves = new LinkedList<>(); // current leaves

        for (int i = 0; i < n; i++) {
            if (degree[i] == 1) leaves.add(i);
        }

        while (!leaves.isEmpty()) {
            // we need to delete all leaves
            lastDelete.clear();
            lastDelete.addAll(leaves);

            int leafCount = leaves.size();
            for (int i = 0; i < leafCount; i++) {
                int leaf = leaves.remove();
                degree[leaf]--; // becomes zero, no need in fact
                for (int possibleNewLeaf : adj[leaf]) {
                    if (--degree[possibleNewLeaf] == 1) {
                        // it's new leaf, the condition includes some special cases
                        // special cases: [[0, 1]], [[0, 1], [0, 2]]
                        leaves.add(possibleNewLeaf);
                    }
                }
            }
                        
        }
        return lastDelete;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值