【树形 DP】树形 DP 的通用思路

news2025/2/25 6:03:32

题目描述

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

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

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

给你一棵包含   个节点的树,标记为   到  。给定数字   和一个有 条无向边的 edges 列表(每一个边都是一对标签),其中 表示树中节点 之间存在一条无向边。

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

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

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

示例 1: alt

输入:n = 4, edges = [[1,0],[1,2],[1,3]]

输出:[1]

解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。

示例 2: alt

输入:n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]

输出:[3,4]

提示:

  • 所有 互不相同
  • 给定的输入保证是一棵树,并且不会有重复的边

树形 DP

这是一道树形 DP 模板题。

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

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

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

alt

树形 DP 问题通常将问题根据「方向」进行划分。

对于当前处理到的节点 u 而言,我们根据是否考虑「从 fau 的出边」将其分为「往上」和「往下」两个方向。

假设我们可以通过 DFS 预处理出 数组和 数组:

  • 代表在以 号点为根节点的树中,以 u 节点为子树根节点时,往下的最大高度
  • 代表在以 号点为根节点的树中,以 u 节点为子节点时,往上的最大高度

那么最终以 u 为根节点的最大高度为

只需要简单的 DFS 即可处理出来。对于 而言,其同样包含「往上」和「往下」两部分:

  • 对于经过 fa 后接着往上的部分有
  • 对于经过 fa 后转而往下的部分,我们需要考虑「 fa 节点往下的最大值 」是否由 u 节点参与而来进行分情况讨论:
    • 如果 本身不由 u 参与,那么 应当是 fa 节点往下的最大值 而来( 代表加上 fau 的边)
    • 如果本身 fa 往下的最大值由 u 节点参与,此时应当使用 fa 往下的次大值 来更新

因此我们需要对 数组进行拆分,拆分为记录「最大值的 数组」和记录「次大值的 数组(注意这里的次大值是非严格的次大值)」,同时使用 数组记录下取得 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);
        }
    }
}
  • 时间复杂度:
  • 空间复杂度:

补充

可能会初次接触「树形 DP」的同学不太理解,这里再补充说明一下。

归根结底,以 u 为根节点的最大深度,必然是下面三种情况之一(往下、往上 和 往上再往下)。

其中对 数组的拆分(变为 )以及记录取得 对应的子节点 ,目的都是为了能够正确统计「往上再往下」的情况(统计该情况时,不能考虑从 fa 经过 u 的路径,因此需要记录一个非严格的次大值 )。

alt

最后

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

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

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

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

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1027677.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

24.(地图工具篇)geoserver热力图层SLD样式效果

地图之家总目录(订阅之前必须详细了解该博客) 示例效果 一:SLD代码 <?xml version="1.0" encoding="ISO-8859-1"?><StyledLayerDescriptor version="1.0.0"xsi:schemaLocation

【UE 粒子练习】05——创建光束类型粒子

效果 步骤 1. 新建一个材质&#xff0c;这里命名为“Mat_Beam” 设置材质域为表面&#xff0c;混合模式为半透明&#xff0c;着色模型为无光照 材质节点如下&#xff1a; 2. 新建一个粒子系统&#xff0c;命名为“P_Beam” 打开“P_Beam”&#xff0c;在发射器中新建一个光束数…

无线振弦采集仪应用隧道安全监测的方案解析

无线振弦采集仪应用隧道安全监测的方案解析 隧道是交通建设中重要的组成部分&#xff0c;安全监测是保障隧道使用安全的重要手段。无线振弦采集仪可以对隧道进行实时、连续的振动监测&#xff0c;提供精确的数据分析和预警&#xff0c;是隧道安全监测的有效工具。 无线振弦采…

DolphinDB x 龙蜥社区,打造多样化的数据底座

近日&#xff0c;浙江智臾科技有限公司&#xff08;以下简称“DolphinDB”&#xff09;正式签署 CLA 贡献者许可协议&#xff0c;加入龙蜥社区&#xff08;OpenAnolis&#xff09;。 DolphinDB 主创团队从 2012 年开始投入研发产品。作为一款基于高性能时序数据库&#xff0c;D…

mysql集群使用nginx配置负载均衡

参考链接&#xff1a;https://mu-sl.com//archives/mysql%E9%9B%86%E7%BE%A4%E4%BD%BF%E7%94%A8nginx%E9%85%8D%E7%BD%AE%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1 配置文件nginx_tcp.conf 示例 load_module modules/ngx_stream_module.so;stream{upstream tcpssh{hash $remote_…

【css】如何实现自定义滚动悬浮置顶、固定表头

说到固定表头或者滚动置顶&#xff0c;我们需要认识css的两个api的2个属性&#xff1a; position: sticky; position: sticky; 是 CSS 中的一种定位方式。当应用于元素时&#xff0c;该元素在滚动时会固定在父容器的指定位置&#xff0c;直到滚动到达特定的位置或条件满足后&…

数据库设计与建模

数据库设计与建模 1 数据库设计的三范式2 数据库建模2.1 建模工具2.2 使用pd建模 1 数据库设计的三范式 三范式&#xff1a; 1.第一范式&#xff08;1NF&#xff09;&#xff1a;确保每一列的原子性&#xff08;做到每列不可拆分&#xff09;2.第二范式&#xff08;2NF&#xf…

从一到无穷大 #17 Db2 Event Store,A Purpose-Built IoT Database Engine

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 引言Architectural overviewData format and meta-dataEnsuring fast ingestionMulti…

【山河送书第十二期】:《巧用ChatGPT快速搞定数据分析》参与活动,送书两本!!

【山河送书第十二期】&#xff1a;《巧用ChatGPT快速搞定数据分析》参与活动&#xff0c;送书两本&#xff01;&#xff01; 关键亮点内容简介作者简介购买链接参与方式往期赠书回顾 关键亮点 用ChatGPT颠覆数据分析&#xff0c;1分钟生成数据分析结果&#xff01; 30多个精心挑…

山洪灾害监测预警系统解决方案

一、方案背景 近几年我国频繁发生山洪灾害现象&#xff0c;造成大量的人员伤亡&#xff0c;使得洪涝灾害死亡总人数呈上升趋势&#xff0c;群死群伤事件时有发生。为了提高山洪灾害监测预警能力&#xff0c;加强灾害发生时的快速反应能力&#xff0c;我司研发出了山洪灾害监测预…

C语言利用联合体判断大小端

#include<stdio.h>#define SIZE(a) (sizeof(a)) union {/* data */short s;char c[SIZE(short)];short b;int d;char f; }un; int main(int argc,char **argv) {un.s 0x0102;if(SIZE(short) 2)//表示short是16位{printf("\n");printf("c[0] %d,c[1…

QT:使用行编辑器、滑动条、滚动条、进度条、定时器

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QLineEdit> //行编辑器 #include <QSlider> //滑动条 #include <QScrollBar> //滚动条 #include <QProgressBar> //进度条 #include <QTimer> …

机器学习 day33(误差分析、添加数据、迁移学习)

误差分析 我们可以手动查看分类错误的子集样本&#xff08;通常为100个&#xff09;&#xff0c;并统计他们的错误类型在所有错误类型中&#xff0c;选择一种或几种最常见的错误&#xff0c;进行改进。这可以最高效的改进你的模型误差分析的一个限制是&#xff1a;它只能很好…

微服务08-认识和使用SpringAMQP

1.AMQP的认识 1.1 介绍 AMQP是什么&#xff1f;看完你就知道了_hello_读书就是赚钱的博客-CSDN博客_amqp 好处&#xff1a; 什么connection&#xff1a;消息队列的连接、channel&#xff1a;服务发送接收消息的通道、Queue&#xff1a;消息队列——>这些你都不需要自己编写…

Keil 5 或者Keil 4自定义主题颜色100%成功

文章目录 步骤一:代码内容解析&#xff1a;完整文件代码一效果图&#xff1a;黑主题Keil 原主题 步骤一: 找到keil 5或者Keil 4软件安装目录下的UV4文件夹下的global.prop文件&#xff0c;然后用记事本打开该文件&#xff0c;复制下面配置替换到global.prop文件里的所有内容保…

IO口电路种类

文章目录 参考1.高速振荡电路&#xff08;时钟IO引脚&#xff09;2.与 GPIO 功能共享的低速振荡电路&#xff08;子时钟IO&#xff09;3.CMOS 滞后输入引脚4.电源输入保护电路5.A/D 转换器 ref (AVRH)带保护电路的电源输入端6.CMOS 电平输出7.CMOS 电平输出&#xff0c;带有模…

键盘失灵按什么键恢复?详细方法分享!

“我的电脑键盘莫名其妙失灵了&#xff0c;试了好多方法都无法恢复。请问遇到键盘失灵的情况&#xff0c;应该按什么键才能恢复呢&#xff1f;” 键盘是计算机的重要输入设备之一&#xff0c;但有时候它可能会出现失灵的情况&#xff0c;让用户感到困惑和不知所措。但其实&…

软件设计师笔记系列(一)

&#x1f600;前言 在日常生活和工作中&#xff0c;我们依赖于各种各样的计算机系统来完成一系列复杂的任务。计算机系统不仅仅是硬件设备的集合&#xff0c;它还包括一系列用于协调硬件工作的软件和协议。了解计算机系统的基础知识&#xff0c;包括其构造和功能&#xff0c;是…

【已解决】模糊匹配导致一门课存在多个同名教师

[已解决] 模糊匹配导致一门课存在多个同名教师 问题 LEFT JOIN jsxxb ON XSKB.RKJSXM LIKE jsxxb.JZGXM || ‘%’ 思路 利用正则表达式解决 jsxxb.JZGXM 的字段示例如 李志勇,许蕤 需以&#xff0c;作为分割点&#xff0c;只匹配逗号前面的名字&#xff0c;或者是没有逗号&a…

CSS 学习笔记(基础)

用来控制网页表现的语言&#xff0c;CSS&#xff08;Cascading Style Sheet&#xff09;&#xff1a;层叠样式表。然后我们继续看看 W3C 标准&#xff1a; 结构&#xff1a;HTML表现&#xff1a;CSS行为&#xff1a;JavaScript CSS导入方式、选择器&属性 由于网页的框架…