310. 最小高度树 : 树形 DP 模板题

简介: 310. 最小高度树 : 树形 DP 模板题

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 310. 最小高度树 ,难度为 中等


Tag : 「树形 DP」、「DFS」、「动态规划」


树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。


给你一棵包含 nn 个节点的树,标记为 00n - 1n1 。给定数字 nn 和一个有 n - 1n1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [a_i, b_i]edges[i]=[ai,bi] 表示树中节点 a_iaib_ibi 之间存在一条无向边。


可选择树中任何一个节点作为根。当选择节点 xx 作为根节点时,设结果树的高度为 hh 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。


请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。


树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。


示例 1:

网络异常,图片无法展示
|


输入:n = 4, edges = [[1,0],[1,2],[1,3]]
输出:[1]
解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。
复制代码


示例 2:

网络异常,图片无法展示
|


输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
输出:[3,4]
复制代码


提示:


  • 1 <= n <= 2 * 10^41<=n<=2104
  • edges.length == n - 1edges.length==n1
  • 0 <= ai, bi < n0<=ai,bi<n
  • ai != biai!=bi
  • 所有 (ai, bi)(ai,bi) 互不相同
  • 给定的输入保证是一棵树,并且不会有重复的边


树形 DP



这是一道树形 DP 模板题。


当确定以某个点为根节点时,整棵树的形态唯一固定,不妨以编号为 00 的节点作为根节点进行分析。


假设当前处理到的节点为 u,其是从父节点 fa 遍历而来,且将要遍历的子节点为 j


即树的形态如图所示(一些可能有的出边用虚线表示):


网络异常,图片无法展示
|


树形 DP 问题通常将问题根据「方向」进行划分,对于当前处理到的节点 u 而言,我们根据是否考虑「从 fau 的出边」将其分为「向下」和「向上」两个方向。


假设我们通过 DFS 预处理出 ff 数组和 gg 数组,其中 f[u]f[u] 代表在以 00 号点为根节点的树中,以 u 节点为子树根节点时,往下的最大高度;g[u]g[u] 代表在以 00 号点为根节点的树中,以 u 节点为子节点时,往上的最大高度。那么最终以 u 为根节点的最大高度为 \max(f[u], g[u])max(f[u],g[u])


其中 f[u]f[u] 只需要简单的 DFS 即可处理出来,但对于 g[u]g[u] 而言,其同样包含「往上」和「往下」两部分,对于往上的部分 \max(g[u], g[fa] + 1)max(g[u],g[fa]+1),对于往下的部分,我们需要考虑「fa 节点往下的最大值 f[fa]f[fa]」是否由 u 节点参与而来进行分情况讨论,如果 f[fa]f[fa] 本身不由 u 参与,那么 g[u]g[u] 应当是 fa 节点往下的最大值 +1+1 而来;如果本身 fa 往下的最大值由 u 节点参与,此时应当使用 fa 往下的次大值 +1+1 来更新 g[u]g[u]


因此我们需要对 ff 数组进行拆分,拆分为记录「最大值的 f1f1 数组」和记录「次大值的 f2f2 数组」,同时使用 pp 数组记录下取得 f1[u]f1[u]u 的子节点 j 为何值。


另外实现上,在处理「往上」方向的 DFS 时,为避免对 fa 节点为空的处理,我们可以将「用 fa 来更新 u」调整为「用 u 来更新 j」。


代码:


class Solution {
    int N = 20010, M = N * 2, idx = 0;
    int[] he = new int[N], e = new int[M], ne = new int[M];
    int[] f1 = new int[N], f2 = new int[N], g = new int[N], p = new int[N];
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx++;
    }
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        Arrays.fill(he, -1);
        for (int[] e : edges) {
            int a = e[0], b = e[1];
            add(a, b); add(b, a);
        }
        dfs1(0, -1);
        dfs2(0, -1);
        List<Integer> ans = new ArrayList<>();
        int min = n;
        for (int i = 0; i < n; i++) {
            int cur = Math.max(f1[i], g[i]);
            if (cur < min) {
                min = cur;
                ans.clear();
                ans.add(i);
            } else if (cur == min) {
                ans.add(i);
            }
        }
        return ans;
    }
    int dfs1(int u, int fa) {
        for (int i = he[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (j == fa) continue;
            int sub = dfs1(j, u) + 1;
            if (sub > f1[u]) {
                f2[u] = f1[u];
                f1[u] = sub;
                p[u] = j;
            } else if (sub > f2[u]) {
                f2[u] = sub;
            }
        }
        return f1[u];
    }
    void dfs2(int u, int fa) {
        for (int i = he[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (j == fa) continue;
            if (p[u] != j) g[j] = Math.max(g[j], f1[u] + 1);
            else g[j] = Math.max(g[j], f2[u] + 1);
            g[j] = Math.max(g[j], g[u] + 1);
            dfs2(j, u);
        }
    }
}
复制代码


  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.310 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
安全 Linux 数据安全/隐私保护
Intel TME和MKTME技术解析
# 市场需求 人们对透明全内存加密这个功能的需求主要来自对机密和敏感数据的保护。普通RAM里面储存的数据,在掉电之后,一般都以为是彻底消失了。但其实在一些复杂的离线攻击下,这些数据仍然是能被恢复出来并导致泄密;而持久性存储器(即外存,包括磁盘、SSD、eMMC等)的数据更加容易泄露。这些设备可能有硬件锁机制的保护,但是用户其实希望的是更细粒度的保护,比如per进程/容器/VM级的。 Int
5726 0
Intel TME和MKTME技术解析
|
5月前
|
人工智能 自然语言处理 安全
HarmonyOS NEXT+AI打造智能助手APP(适配DeepSeek)
华为仓颉编程语言与HarmonyOS NEXT结合AI大模型,开创智能助手APP开发新纪元。仓颉语言以自然化编程降低门槛,HarmonyOS NEXT提供流畅安全的系统支持,AI大模型赋予助手强大交互能力。实战课程覆盖智能对话、写作、画图等6大核心业务,模块化开发助你掌握全流程技能。参考资料及开源教程助力学习,开启智能应用开发新篇章。
309 10
HarmonyOS NEXT+AI打造智能助手APP(适配DeepSeek)
|
12月前
|
安全 关系型数据库 MySQL
Linux下安装mysql8.0(以tar.xz包安装--编译安装)
通过上述步骤,您完成了从下载、编译、安装到配置MySQL 8.0的全过程。此过程虽然较为复杂,但提供了对MySQL安装环境的完全控制,有助于满足特定的部署需求。在实际操作中,根据具体的系统环境,可能还需调整部分步骤或解决未预见的依赖问题。始终参考官方文档和社区资源,保持安装过程与最新版本的兼容性。
4202 68
|
12月前
|
存储 数据管理 数据安全/隐私保护
Vuex 和 LocalStorage 实现数据共享
【10月更文挑战第8天】
158 1
|
安全 网络协议
http协议的有效字符
HTTP协议中有效的字符集主要是ASCII字符,包括字母、数字、保留字符、子定界符,以及一些需转义的不安全字符。使用这些字符时,应该保证正确的编码和字符集的使用,以维护HTTP交流的准确性和安全性。当处理URI和构建HTTP请求时,对特殊字符进行适当的编码是至关重要的,以确保信息的无误传达和服务器的正确理解。在现代的网络通讯中,这些细节成为了保障交互效率和系统安全的基石。
406 0
|
存储 缓存 Python
解密 Python 元组的实现原理
解密 Python 元组的实现原理
205 4
|
数据采集 存储 数据可视化
【优秀python数据分析案例】基于python的中国天气网数据采集与可视化分析的设计与实现
本文介绍了一个基于Python的中国天气网数据采集与可视化分析系统,通过requests和BeautifulSoup库实现数据爬取,利用matplotlib、numpy和pandas进行数据可视化,提供了温湿度变化曲线、空气质量图、风向雷达图等分析结果,有效预测和展示了未来天气信息。
3382 3
基于蓄电池和飞轮混合储能系统的SIMULINK建模与仿真
构建了基于SIMULINK的蓄电池-飞轮混合储能系统模型,重点在于飞轮模型与控制策略。仿真展示了充放电电流电压、功率波形及交流负载端的电气参数变化,揭示了系统从波动到稳定的过程。 ### 系统原理 - 混合储能系统结合了蓄电池(化学能转换)和飞轮(动能存储)的优势,提供高效快速的能量响应。 - 蓄电池通过化学反应进行能量储存和释放。 - 飞轮储能利用电动机/发电机转换动能和电能。 - 智能控制协调二者工作,适应电力系统需求,提升系统性能。 ### 混合储能原理 混合系统利用控制系统协同蓄电池和飞轮,优化充电和放电,以提高储能效率和电力系统的整体表现,预示着其未来广泛应用的潜力。
|
存储 运维 数据中心
Docker 的基本概念和优势,以及在应用程序开发中的实际应用。
Docker是容器化技术,基于镜像(只读模板)创建可移植的容器,确保应用及其服务在隔离环境中运行。其优势包括快速部署(整个应用打包一次部署)、跨平台兼容、统一运行环境、资源隔离和简化依赖管理。Docker在开发和运维中都发挥作用,助力高效测试、部署和提升生产稳定性。
281 3