Java&C++题解与拓展——leetcode310.最小高度树【复习链式前向星】

每日一题做题记录,参考官方和三叶的题解

题目要求

在这里插入图片描述

思路一:动态规划

遍历整个图,当前遍历节点为 c u r cur cur,从父节点 f a t h e r father father遍历而来,将要遍历子节点 j j j

要计算以 c u r cur cur为根的树的最小高度,就是找到 c u r cur cur最远的叶子节点的距离,这个过程可以分为上(父节点方向)和下(子节点方向)两个方向,寻找过程通过DFS实现,向上的最长距离记 u p [ c u r ] up[cur] up[cur],向下的最长距离记 d o w n [ c u r ] down[cur] down[cur],那么最小高度即为 m a x ( u p [ c u r ] , d o w n [ c u r ] ) max(up[cur],down[cur]) max(up[cur],down[cur])

  • u p up up】:向上寻找时要分为两个部分,一个是找父节点的父节点方向(祖辈),另一个是寻找父节点的其他子节点方向(兄弟)。
    • 祖辈方向的结果为 m a x ( u p [ c u r ] , u p [ f a t h e r ] + 1 ) max(up[cur], up[father] + 1) max(up[cur],up[father]+1),即当前节点的向上最大值,或父节点的向上最大值加上到 c u r cur cur 1 1 1
    • 兄弟方向的结果需考虑 f a t h e r father father向下最大值是否经过了 c u r cur cur,若未经过则直接更新为 d o w n [ f a t h e r ] + 1 down[father] + 1 down[father]+1,若经过了则要换次长的路径长度加 1 1 1
  • d o w n down down】:向下的寻找直接遍历即可,根据向上兄弟方向寻找时的需求,需分别记录向下最长的路径长度 d o w n M a x downMax downMax和次长的路径长度 d o w n S e c downSec downSec,并维护最长路径所经过的第一个子节点 i d x M a x idxMax idxMax

在向上进行遍历时,为了避免父节点为空,不用 f a t h e r father father更新 c u r cur cur,而用 c u r cur cur更新 j j j

Java

class Solution {
    int N = 20001, M = N * 2, idx = 0;
    int[] head = new int[M], edge = new int[M], next = new int[M];
    int[] downMax = new int[N], downSec = new int[N], up = new int[N], idxMax = new int[N];
    void add(int a, int b) { //链式前向星
        edge[idx] = b;
        next[idx] = head[a];
        head[a] = idx++;
    }

    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        Arrays.fill(head, -1);
        for(int[] e : edges) { //建图
            int a = e[0], b = e[1];
            add(a, b);
            add(b, a);
        }
        dfsD(0, -1);
        dfsU(0, -1);
        List<Integer> res = new ArrayList<>();
        int min = n;
        for(int i = 0; i < n; i++) {
            int cur = Math.max(downMax[i], up[i]);
            if(cur < min) {
                min = cur;
                res.clear();
                res.add(i);
            }
            else if(cur == min)
                res.add(i);
        }
        return res;
    }

    //下方的最大高度
    int dfsD(int cur, int father) {
        for(int i = head[cur]; i != -1; i = next[i]) {
            int j = edge[i];
            if(j == father)
                continue;
            int sub = dfsD(j, cur) + 1; //子树高度
            if(sub > downMax[cur]) { //更新最大
                downSec[cur] = downMax[cur];
                downMax[cur] = sub;
                idxMax[cur] = j;
            }
            else if(sub > downSec[cur]) //更新次大
                downSec[cur] = sub;
        }
        return downMax[cur];
    }

    //上方的最大高度
    void dfsU(int cur, int father) {
        for(int i = head[cur]; i != -1; i = next[i]) {
            int j = edge[i];
            if(j == father)
                continue;
            if(idxMax[cur] != j) //最长路径不经过j
                up[j] = Math.max(up[j], downMax[cur] + 1);
            else //经过j
                up[j] = Math.max(up[j], downSec[cur] + 1);
            up[j] = Math.max(up[j], up[cur] + 1);
            dfsU(j, cur);
        }
    }
}
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

链式前向星

  • 前几天有学习过, 这篇文章 末尾有三叶姐姐更详细的介绍;
  • 数组替代邻接表,head替代头指针,edge串连相邻节点,next替代next指针,因为边有方向后两个数组大小为head大小二倍。

C++

❓ ❓ ❓
这个地方会报堆栈溢出的错,明明和Java代码没什么区别,怀疑是递归太深或者是建图的问题,不太好解决就先放着了。

class Solution {
private:
    const static int N = 20010, M = N * 2;
    int idx = 0;
    int head[N], edge[M], next[M];
    int downMax[N], downSec[N], up[N], idxMax[N];
    void add(int a, int b) { //链式前向星
        edge[idx] = b;
        next[idx] = head[a];
        head[a] = idx++;
    }
public:
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        memset(head, -1, N);
        for(auto& e : edges) { //建图
            int a = e[0], b = e[1];
            add(a, b);
            add(b, a);
        }
        dfsD(0, -1);
        dfsU(0, -1);
        vector<int> res;
        int min = n;
        for(int i = 0; i < n; i++) {
            int cur = max(downMax[i], up[i]);
            if(cur < min) {
                min = cur;
                res.clear();
                res.push_back(i);
            }
            else if(cur == min)
                res.push_back(i);
        }
        return res;
    }

    //下方的最大高度
    int dfsD(int cur, int father) {
        for(int i = head[cur]; i != -1; i = next[i]) {
            int j = edge[i];
            if(j == father)
                continue;
            int sub = dfsD(j, cur) + 1; //子树高度
            if(sub > downMax[cur]) { //更新最大
                downSec[cur] = downMax[cur];
                downMax[cur] = sub;
                idxMax[cur] = j;
            }
            else if(sub > downSec[cur]) //更新次大
                downSec[cur] = sub;
        }
        return downMax[cur];
    }

    //上方的最大高度
    void dfsU(int cur, int father) {
        for(int i = head[cur]; i != -1; i = next[i]) {
            int j = edge[i];
            if(j == father)
                continue;
            if(idxMax[cur] != j) //最长路径不经过j
                up[j] = max(up[j], downMax[cur] + 1);
            else //经过j
                up[j] = max(up[j], downSec[cur] + 1);
            up[j] = max(up[j], up[cur] + 1);
            dfsU(j, cur);
        }
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

思路二:拓扑排序+BFS

设树中相距最远的两个节点为 ( x , y ) (x,y) (x,y),它们之间的距离为 d i s t a n c e = d i s [ x ] [ y ] distance = dis[x][y] distance=dis[x][y],那么所求的根节点即为这条路径上中间的节点。由于是距离最远的,二者必然均为叶子节点(度为1),删除 x x x y y y,又会出现新的叶子节点,依次删除最终留下最中间的节点,即为所求。

  • 将所有度为 1 1 1的节点添加至队列,并记录剩余节点 r e m a i n C n t = n remainCnt=n remainCnt=n
  • 遍历队列依次更新这些节点的父辈节点,并将新的度为 1 1 1的节点添加至队列,并从 r e m a i n C n t remainCnt remainCnt中减去;
  • 直至 r e m a i n C n t ≤ 2 remainCnt\le 2 remainCnt2,即剩下最中间的一个or两个节点,即为所求。

Java

class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        List<Integer> res = new ArrayList<Integer>();
        if(n == 1) {
            res.add(0);
            return res;
        }
        int[] degree = new int[n];
        List<Integer>[] adj = new List[n];
        for(int i = 0; i < n; i++)
            adj[i] = new ArrayList<Integer>();
        for(int[] e : edges) { //建图并统计度
            adj[e[0]].add(e[1]);
            adj[e[1]].add(e[0]);
            degree[e[0]]++;
            degree[e[1]]++;
        }

        Queue<Integer> que = new ArrayDeque<Integer>(); //度为1的节点
        for(int i = 0; i < n; i++)
            if(degree[i] == 1)
                que.offer(i);

        int remainCnt = n;
        while(remainCnt > 2) {
            int len = que.size();
            remainCnt -= len;
            for(int i = 0; i < len; i++) {
                int cur = que.poll();
                for(int v : adj[cur]) {
                    degree[v]--;
                    if(degree[v] == 1)
                        que.offer(v);
                }
            }
        }
        while(!que.isEmpty())
            res.add(que.poll());

        return res;
    }
}
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

C++

class Solution {
public:
    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        if(n == 1)
            return {0};
        
        vector<int> degree(n);
        vector<vector<int>> adj(n);
        for(auto& e : edges) { //建图并统计度
            adj[e[0]].emplace_back(e[1]);
            adj[e[1]].emplace_back(e[0]);
            degree[e[0]]++;
            degree[e[1]]++;
        }
        queue<int> que;  //存放度为1的节点
        vector<int> res;
        for(int i = 0; i < n; i++)
            if(degree[i] == 1)
                que.emplace(i);

        int remainCnt = n;
        while(remainCnt > 2) {
            int len = que.size();
            remainCnt -= len;
            for(int i = 0; i < len; i++) {
                int cur = que.front();
                que.pop();
                for(auto& v : adj[cur]) {
                    if(--degree[v] == 1)
                        que.emplace(v);
                }
            }
        }
         while(!que.empty()){
             res.emplace_back(que.front());
             que.pop();
         }
         return res;
    }
};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

总结

复习了一下链式前向星存图,发现还不能很熟练地无脑写,就又搜着学习了下。思路一向上和向下分别计算的思路很强,似乎是树形DP的模板题,可以多看看背背。

拓扑排序的证明过程叙述比较简略,因为觉得还挺好理解的。


欢迎指正与讨论!
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值