310 最小高度树(树形dp)

1. 问题描述:

树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,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]

示例 3:

输入:n = 1, edges = []
输出:[0]

示例 4:

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

提示:

1 <= n <= 2 * 10 ^ 4
edges.length == n - 1
0 <= ai, bi < n
ai != bi
所有 (ai, bi) 互不相同
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-height-trees

2. 思路分析:

① 这道题目属于树形dp的经典题目,分析题目可以知道我们需要求解出所有以当前节点为根到达其余节点的距离,并且求解的是最小的距离,我们可以先画出具体的图形帮助我们理解。我们需要求解出从当前的节点往上走与往下的最大距离就是当前节点为根的最大高度。以下图为例,我们任意选取一个节点b,往下走的最大距离为2,往上走的最大距离也为2,往下走的意思其实很好理解,关键是往上走,节点b往上走表示的是节点a往上走与节点a往下走的最大距离,其实将下图转换为b作为根节点就很好理解往上走与往下走的意思了。这里需要注意的一个问题是当节点a往下走的时候可能最大的距离就是b往下走的路径对应的距离这种路径是不合法的,为了解决这个问题我们声明四个列表d1,d2,up,p(python语言使用列表表示),d1[u]表示以当前的u节点为根往下走的最大距离,d2[u]表示以当前的u节点为根往下走出来最大距离除外的次大距离,这里声明d2[u]的目的就是用来解决求解a节点往下走的最大距离就是b往下走的路径对应的最大距离,所以这里记录的是次大距离这样就可以避免了不合法的路径(来回折返),p[u]表示以当前节点u为根往下走最大距离对应路径的子节点编号,例如b往下走的最大路径为b-f-g,所以p[b] = t,例如b往上走的过程中需要计算a往下走的最大距离,这个时候a往下走的最大距离对应的路径就是b往下走的路径对应的最大距离所以在往上走的时候通过判断p[b] == t就可以判断a节点往下走是否是合法的路径,不合法的时候我们取最大距离。

② 树形dp一般使用dfs解决,一般分为节点往上走与节点往下走的两种写法,对于这道题目需要写两遍dfs,分别求解往上走与往下走的最大距离。节点往下走的时候的最大距离其实很好求解,我们在dfs方法调用完之后再求解子节点的中最大距离那个即可(这里是使用到了递归层层返回的特点来求解当前节点的最大高度),这样一遍dfs即可求解出所有节点往下走的最大路径,在节点往下走的dfs过程中我们需要维护节点往下走的最大值与次大值,最大值对应的子节点编号。如果当前的子节点往下走的时候对应距离更大,那么更新当前根节点的最大距离,次大距离,当前最大距离对应的子节点

③ 因为题目中给出的是节点与节点之间的边,所以需要先创建邻接表来存储图,因为使用的是python语言所以使用字典来创建邻接表,键表示节点标号,值为list类型表示当前节点的子节点编号。

3. 代码如下:

from typing import List
import collections


class Solution:
    # d1记录往下走的最大距离, d2记录的是往下走的次大距离, p1记录的是往下走最大距离对应的子节点, up记录往上走的最大距离
    g = d1 = d2 = p1 = up = None
    
    # 求解往下走的最大记录, 因为是无向图所以使用father参数来表示当前节点的父节点这样可以避免重复搜索
    def dfs1(self, u: int, father: int):
        for x in self.g[u]:
            if x == father: continue
            self.dfs1(x, u)
            d = self.d1[x] + 1
            # 以当前子节点往下走的距离更大那么就更新对应的值
            if d >= self.d1[u]:
                self.d2[u] = self.d1[u]
                self.d1[u] = d
                self.p1[u] = x
            elif d > self.d2[u]:
                self.d2[u] = d
    
    # 求解往上走的最大距离
    def dfs2(self, u: int, father: int):
        for x in self.g[u]:
            if x == father: continue
            if self.p1[u] == x:
                self.up[x] = max(self.up[u], self.d2[u]) + 1
            else:
                self.up[x] = max(self.up[u], self.d1[u]) + 1
            self.dfs2(x, u)

    def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]:
        # 需要在这个方法声明下面的变量为了在每一次调用这个方法的时候全局变量都是新的列表这样每一次调用方法的时候就不会造成上一次调用这个方法的结果会影响到下一次的结果
        self.d1 = [0] * n
        self.d2 = [0] * n
        self.p1 = [0] * n
        self.up = [0] * n
        # 使用字典来创建邻接表
        self.g = collections.defaultdict(list)
        for e in edges:
            a, b = e[0], e[1]
            self.g[a].append(b)
            self.g[b].append(a)
        self.dfs1(0, -1)
        self.dfs2(0, -1)
        mind = n + 1
        res = list()
        for i in range(n):
            mind = min(mind, max(self.up[i], self.d1[i]))
        for i in range(n):
            if max(self.up[i], self.d1[i]) == mind:
                res.append(i)
        return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值