Leetcode——310. 最小高度树

概述

题目连接

  • 选择树中任何一个节点作为根,求可以构造最小高度的树的根节点的结点编号

分析

搜索法

根据题目,容易想到通过选择不同的结点来构造树,然后利用树的层次遍历(即BFS)来获得该树的高度

结论法

实际上,该题是个结论题,需要一定的背景知识

结论:

设 $\textit{dist}[x][y] 表 示 从 节 点 x 到 节 点 y 的 距 离 , 假 设 树 中 距 离 最 长 的 两 个 节 点 为 表示从节点 x到节点 y的距离,假设树中距离最长的两个节点为 xy (x,y)$,它们之间的距离为 maxdist = dist [ x ] [ y ] \textit{maxdist} = \textit{dist}[x][y] maxdist=dist[x][y],则可以推出以任意节点构成的树最小高度一定为 m i n h e i g h t = ⌈ m a x d i s t 2 ⌉ minheight=\left \lceil \dfrac{maxdist}{2} \right \rceil minheight=2maxdist,且最小高度的树根节点一定在 节点 x到节点 y的路径上。

  • 证明思路大致如下:

    将最长链称之为主链。主链上任意节点可以将主链分为包含该节点的上下两部分,可以有以下几个结论:

    1. 该节点的任意旁路分支最大深度都不能超过主链短的那部分的长度,否则最长链选择错误

    2. 以该节点为根的树的最大深度为主链长的那部分的长度

      如果不等于,则与最长链矛盾

    3. 以该节点旁路分支上的任意节点的为根的树高度不会小于 主链长的部分+1,所以不可以选择非主链上的节点作为根

    4. 选择中点为根是主链较长部分长度最小的策略,也就是最优解

思路

搜索法

  • 根据输入的形式,如何选择保存图的结构?

    • 这里建议使用邻接表法,因为数据量很大 2 ∗ 1 0 4 2*10^4 2104,如果使用邻接矩阵空间占用较大
  • 如何保存每个结点作为根获得的树高,并且最终得到最小高度的根结点?

    • 因为可能有多个解,所以选择

      std::map<int, vector<int>> res_record;
      

      第一位记录高度,第二位记录根结点编号

结论法

  • 如何找到树中距离最长的两个结点呢?

  • 如何获得两个最远结点的中间的路径呢?

    • 一种思路是找到确定两个结点后,再利用搜索策略,获得路径
    • 其实可以在找两个结点的过程中记录该路径,具体实现可以参考下面代码注释

代码

搜索代码

class Solution {
public:

    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        vector<vector<int>> map(n, vector<int>(n));
        for (auto &p : edges) {		// 邻接矩阵保存图
            map[p[0]][p[1]] = 1;
            map[p[1]][p[0]] = 1;
        }
        auto temp_map = map;		// 邻接矩阵副本

        std::map<int, vector<int>> res_record;
        for (int i = 0; i < n; ++i) {		// 遍历每个结点,作为树更
            queue<int> record;		
            record.push(i);
            int height = -1;
        
            while(!record.empty()) {		// BFS获取深度
                ++height;		// 记录深度

                int size = record.size();
                while(size--) {
                    int t = record.front(); record.pop();
                    for (int j = 0; j < n; ++j) {		// 广度遍历该结点的所有邻接节点
                        if (map[t][j] == 1) {
                            map[j][t] = 0;
                            record.push(j);
                        }
                    }
                }
            }
            res_record[height].push_back(i);		// 记录该高度下的根结点
            map = temp_map;
        }
        return res_record.begin()->second;		// 输出最小高度的根结点
    }
};
  • 我们知道使用邻接矩阵保存的图,进行搜索,其复杂度为 O ( n 2 ) O(n^2) O(n2),而我们还要循环每个结点作为根,那么复杂度就是 O ( n 3 ) O(n^3) O(n3),肯定超时

    即使使用邻接表保存图,搜索的复杂度也有 O ( n ) O(n) O(n),整个算法复杂度也是 O ( n 2 ) O(n^2) O(n2),在大规模数据下也会超时

结论法

官方题解代码

class Solution {
public:
    int findLongestNode(int u, vector<int> & parent, vector<vector<int>>& adj) {
        int n = adj.size();
        queue<int> qu;
        qu.emplace(u);
        vector<bool> visit(n);
        visit[u] = true;

        int node = -1;
        while (!qu.empty()) {
            int curr = qu.front();
            qu.pop();
            node = curr;
            for (auto & v : adj[curr]) {
                if (!visit[v]) {
                    visit[v] = true;
                    parent[v] = curr;		// 记录路径
                    qu.emplace(v);
                }
            }
        }
        return node;
    }

    vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
        if (n == 1) {		// 特例,只有一个结点
            return {0};
        }
        vector<vector<int>> adj(n);		// 注意要用邻接表保存图
        for (auto & edge : edges) {
            adj[edge[0]].emplace_back(edge[1]);
            adj[edge[1]].emplace_back(edge[0]);
        }
        
        vector<int> parent(n, -1);		// 用来在确定最远节点时保存路径
        /* 找到与节点 0 最远的节点 x */
        int x = findLongestNode(0, parent, adj);
        /* 找到与节点 x 最远的节点 y */
        int y = findLongestNode(x, parent, adj);
        /* 求出节点 x 到节点 y 的路径 */
        vector<int> path;
        parent[x] = -1;
        while (y != -1) {
            path.emplace_back(y);
            y = parent[y];
        }
        // Find(x,y,false)
        int m = path.size();
        if (m % 2 == 0) {
            return {path[m / 2 - 1], path[m / 2]};
        } else {
            return {path[m / 2]};
        }
    }
};
  • 这里解释以下parent的作用

    • 在通过某个结点找其相邻的节点时,我们通过paretn[v]=cur反向记录了节点v与cur相连,可以确保所有与cur相连的路径都被记录,然后可以反向找到原结点
    • 如果是parent[cur]=v,则只能记录cur和一个节点的连接情况,而该连接不一定是最长路径的的连接

    这里用这种方法比较难想,一般我们用第一种思路:再使用一次搜索来记录路径 就可以了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值