310. 最小高度树

119 阅读4分钟

树形dp dfs

题目

310. 最小高度树

image.png

解析

树形dp

关于题中描述的图,我们可以整理一下,假设以A为根节点,整理成右侧的树形状。

在以A为根节点的树中,通过dfs可以很容易求得每个节点到叶子结点的最大距离。求解方法如下:

通过递归,问每一个孩子要它到叶子结点的最大距离,然后对每一个孩子的信息进行汇总,就可以得到当前节点到叶子结点的最大距离。举个例子:

我们初始化所有节点到叶子结点的距离为0,递归出口就是叶子结点,它不需要收集它孩子的信息,直接返回即可。

所以G节点返回0,然后F节点在其所有孩子返回的结果中选择最大值,再加1,就是F节点到叶子结点的最大距离1。对于B节点来说,它能收集两个孩子返回来的信息,E:0, F:1。所以,B能知道它到叶子结点的最大距离是2,而且是经过F节点到达的。

也就是说,每个节点能够知道两个信息:

  • 到叶子结点的最大距离
  • 这个最大距离是经由哪个孩子到达的

此时,我们是求得了以A节点为根情况下的一些信息。这些都是接下来的资源。

在我们以其他节点为根节点求树高前,需要明确一些内容,以B节点为例。此时,不将树重新绘制,只是想象以B为根节点即可。以B为根节点的路径,可以分为向上和向下两部分。向下的部分我用红色表示,向上的部分我用蓝色表示。

对于B节点向下的路径,到叶子结点的最大距离,我们已经求过了,还能知道是经过F节点。

对于B节点向上的路径,到叶子结点的最大距离,就是A节点向下的最大距离,加上AB之间的距离1。

所以,B节点到所有叶子结点的最大距离,就是以上两种情况取max。

但是,还存在特殊情况。A节点在统计其孩子信息时,能够知道到达叶子结点的最大距离是经由A的,那在计算B节点向上的路径时,A节点向下的路径就不能使用经过B点的这条最大路径,而是要使用A节点向下路径中的第二大距离。

也就是说,在【通过dfs求得每个节点到叶子结点的最大距离】时,还需要记录下第二大距离。

于是B向上的最大路径长度就需要分两种情况:

  • A向下最大路径经过B:up[B] = d2[A] + 1 第二大距离加1
  • A向下最大路径不经过B:up[B] = d1[A] + 1 第一大距离加1

综上,需要分两步求到大叶子结点的距离。

第一步求所有节点向下到达叶子结点的最大距离

第二步求所有节点向上到达叶子结点的最大距离

于是,我们就能够计算每一个节点到所有叶子结点的最大距离了。

在所有节点的最大距离中,再找出最小的。

最后,就是与这个最小值比较,相等就可以加入到结果集中。

答案

var (
   // 第一大距离,第二大距离
   d1, d2 []int
   // 第一大距离经过的孩子
   via []int
   // 向上
   up []int
   // 用map表示邻接链表
   g map[int][]int
)

func findMinHeightTrees(n int, edges [][]int) []int {
   // 初始化
   d1, d2, up, via = make([]int, n, n), make([]int, n, n), make([]int, n, n), make([]int, n, n)
   // 将图转化成邻接链表表示
   g = map[int][]int{}
   for _, pair := range edges {
      g[pair[0]] = append(g[pair[0]], pair[1])
      g[pair[1]] = append(g[pair[1]], pair[0])
   }
   // 第一步求所有节点向下到达叶子结点的最大距离
   dfs1(0, -1)
   // 第二步求所有节点向上到达叶子结点的最大距离
   dfs2(0, -1)
   // 在所有节点的最大距离中,再找出最小的。
   minVal := math.MaxInt
   for i := 0; i < n; i++ {
      minVal = min(minVal, max(up[i], d1[i]))
   }
   // 就是与这个最小值比较,相等就可以加入到结果集中。
   res := []int{}
   for i := 0; i < n; i++ {
      d := max(up[i], d1[i])
      if d == minVal {
         res = append(res, i)
      }
   }
   return res
}

// 因为是无向图,用father表示父节点,避免循环遍历
func dfs1(u int, father int) {
   for _, next := range g[u] {
      if next == father {
         continue
      }
      dfs1(next, u)
      d := d1[next] + 1
      if d > d1[u] {
         d2[u] = d1[u]
         d1[u] = d
         via[u] = next
      } else if d > d2[u] {
         d2[u] = d
      }
   }
}

func dfs2(u int, father int) {
   for _, next := range g[u] {
      if next == father {
         continue
      }
      // - A向下最大路径经过B:up[B] = d2[A] + 1     第二大距离加1
      if via[u] == next {
         up[next] = max(d2[u], up[u]) + 1
      } else {
         // - A向下最大路径不经过B:up[B] = d1[A] + 1    第一大距离加1
         up[next] = max(d1[u], up[u]) + 1
      }
      dfs2(next, u)
   }
}

func max(a, b int) int {
   if a > b {
      return a
   }
   return b
}

func min(a, b int) int {
   if a < b {
      return a
   }
   return b
}

LeetCode记录

‌LeetCode刷题记录

image.png