一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
每日刷题 2021.04.06
- leetcode原题链接:leetcode-cn.com/problems/mi…
- 难度:中等
- 方法:bfs(广度优先遍历)
题目
- 树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
- 给你一棵包含 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]
提示
- 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广度优先遍历算法。