[ bfs ]310. 最小高度树

254 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

每日刷题 2021.04.06

题目

  • 树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
  • 给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。
  • 可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。
  • 请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。
  • 树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。

示例

  • 示例1

image.png

输入:n = 4, edges = [[1,0],[1,2],[1,3]]
输出:[1]
解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。
  • 示例2 image.png
输入: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
输出: [3,4]

提示

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

拓展

  • 数组中的map方法,不会改变原数组,会返回一个新的数组。
    // 函数对数组进行处理,最终以新数组的方式返回
    array.map(function(currentValue,index,arr), thisValue)
    
  • 本题中使用其构造二维数组(创建一个长度为n的数组,将其中每个值初始化为0,再将其每个元素修改为空数组,实现二维数组的创建)
    // 数组中的每一个元素都需要执行这个函数
    let tree = new Array(n).fill(0).map(() => []);
    

解题思路

建图

  • 根据题意可知,树为一个有向无环图,那么分析可知,需要先创建图,再进行后续的操作。
  • 建图:类似于邻接表的样子,不定长的二维数组。以第一个样例建图,获得二维数组如下
    // 样例1建图,二维数组如下:
    // tree[a].push(b);  / tree[b].push(a);
    [
     [1],
     [0,2,3],
     [1],
     [1]
    ]
    
  • 注意⚠️:如果直接开n * n的数组,将存在边的数组元素标记为1,否则为0;这样会包含很多用不上的边,数据足够大的时候,就会溢出。

bfs

  • 根据题意:需要求树的最小高度,那么第一想到的就是找最长的一条路,将其从中间拿起来,这样就可以保证两边的高度相差不多,形成最小高度树。
  • 看到最小、最短这样的描述,树和图都可以考虑使用bfs来书写。
  • 思路对的,但是很遗憾。没有成功的写出代码,因此我转换了另一种思路。
  • 基于上述已经构建好的图,在观察样例,分析可知:
    • 对于无向图中的每个节点来说,不分入度和出度,全部都算为度即可。在建图的过程中,就可以将每个节点的度,记录在一个数组degree
    • 每次删除掉度为1的节点,一直遍历到内部,bfs最后一层遍历到的节点,就是最终的根节点结果。

为什么每次删除度为1的节点呢?

  • 可以想象一下生活中的例子:剥洋葱。是不是一层一层的剥到内部,那么放到这道题上,我们就可以类比成:一层层的删掉当前的叶子节点,最终一层就是根节点。

实现步骤

  • (二维不定长数组)建图
  • bfs常规的操作,记住要保存最后一层入队的节点,最后输出,因为最后一层,不论是几个,都会在队列的最后一层中。即:输出所有可能作为根节点的节点。

dfs\拓扑排序

AC代码

var findMinHeightTrees = function(n, edges) {
  // bfs
  // 创建图:无向图 二维数组存储
  if(n == 1) return [0];
  let tree = new Array(n).fill(0).map(() => []);
  // 如果存在边,将其标记为1
  // 还需要计算度为多少,对于无向图来说,只存在度;而对于有向图来说,存在出度和入度
  let len = edges.length, degree = new Array(n).fill(0);
  for(let i = 0; i < len; i++) {
    let a = edges[i][0],b = edges[i][1];
    degree[a]++;
    degree[b]++;
    tree[a].push(b);
    tree[b].push(a);
  }
  // console.log('tree:', tree)
  // bfs 一层一层的向内遍历
  let queue = [];
  // 需要先将度为1的节点放入到队列中
  for(let i = 0;i < n; i++) {
    if(degree[i] == 1){
      queue.push(i);
    }
  }
  // console.log('qff:', queue)
  // bfs循环叶子节点下面的节点
  let res = [];
  while(queue.length != 0) {
    res.length = 0;
    let lenQ = queue.length;
    for(let i = 0; i < lenQ; i++) {
      let cur = queue.shift();
      res.push(cur)
      // 如何记录下来最后一层的数据呢?
      // console.log('shift:', cur, n)
      let lenC = tree[cur].length;
      for(let j = 0; j < lenC; j++) {
          let node = tree[cur][j];
          degree[node]--;
          if(degree[node] === 1){
            queue.push(node)
          }
      }
    }
  }
  return res;
};

总结

  • 遇到最小、最短这样的关键词,需要马上想到bfs广度优先遍历算法。