开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情
最小高度树
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
给你一棵包含 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) 互不相同
- 给定的输入 保证 是一棵树,并且 不会有重复的边
解题思路:
- 构建链式前向星结构,同时存储每个顶点的出度(用数组保存,下标即为对应的顶点),注意无向图的边数是有向时的两倍
- 遍历出度数组,生成一个由出度为1的顶点组成的数组(保存顶点的index)
- 遍历出度为1的顶点数组,使用链式前向星找到对应的点所在的边
- 找到对应的边后,进行删边操作
- 因为是无向图,需要删除顶点相反时的另一条边,删除后判断顶点是否出度为1
- 5.1 出度为1则加入出度为1的数组;
- 5.2 出度不为1则忽略
- 循环直到边剩余数大于2,因为最后的情况只有可能是有两个顶点(对应两条边)或者一个顶点(无边)
- 最后出度为1的数组中的数就是最小高度数的节点
我的答案:
/**
* @param {number} n
* @param {number[][]} edges
* @return {number[]}
*/
var findMinHeightTrees = function(n, edges) {
const edgeListLen = edges.length;
/** 边界情况 */
if (edgeListLen * 2 === 2) {
return edges[0].sort();
}
if (edgeListLen * 2 === 0) {
return [0];
}
initHead(n);
initOutList(n);
/** 出度为1的顶点 数组 */
const outOneList = [];
/** 构造链式前向星 */
for (let i = 0; i < edgeListLen; i++) {
const cur = edges[i];
addEdges(cur[0], cur[1]);
}
/** 筛选出度为1的顶点 */
for (let i = 0; i < outList.length; i++) {
if (outList[i] === 1) {
outOneList.push(i);
}
}
let elen = edgeListLen * 2;
/** 最后只会有2两条边或者没有 */
while (elen > 2) {
let oneLen = outOneList.length;
elen -= oneLen * 2;
while (oneLen-- > 0) {
const outOne = outOneList.shift();
let edgeS = head[outOne];
let targetS = edgeLinks[edgeS];
if (targetS) {
let edgeE = head[targetS.to];
let targetE = edgeLinks[edgeE];
/** 记录当前边和前一条边 */
let edgeECurr = edgeE;
let edgeEPrev = edgeE;
while (targetE) {
if (targetE.to === outOne) {
/** 判断是否为第一个匹配到的边 */
if (edgeE === edgeECurr) {
if (targetE.next !== -1) {
/** 同一条边的next存在时,把next指向下一条边 */
head[targetS.to] = targetE.next;
/** 下一条边的next为-1, 说明下一条是最后一条,出度只剩下1 */
if (edgeLinks[targetE.next].next === -1) {
/** 加入到出度为1的列表 */
outOneList.push(targetS.to);
}
}
/** 删边 */
delete edgeLinks[edgeE];
} else {
/** 把前一条边的next指向当前边的next */
edgeLinks[edgeEPrev].next = edgeLinks[edgeECurr].next;
/** 下一条边的next为-1, 说明下一条是最后一条,出度只剩下1 */
if (edgeEPrev === edgeE && edgeLinks[edgeEPrev].next === -1) {
/** 加入到出度为1的列表 */
outOneList.push(targetS.to);
}
/** 删边 */
delete edgeLinks[edgeECurr];
}
delete edgeLinks[edgeS];
targetE = undefined;
} else {
if (targetE.next !== -1) {
/** 更新边的位置 */
edgeEPrev = edgeECurr;
edgeECurr = targetE.next;
/** 指向next的位置 */
targetE = edgeLinks[targetE.next];
} else {
targetE = undefined;
}
}
}
}
}
}
return outOneList.sort();
};
/** edges index */
let cnt = 0;
/** 顶点对应的最后一次输入的边的index */
let head = [];
/** 顶点的出度 数组 */
let outList = [];
/** 链式前向星结构*/
const edgeLinks = [];
/**
*
* 无向图:s既可以是起点也是终点,v既可以是终点也可以是起点。一条边要算作两条边
* @param {*} s 有向图:起点;无向图:起点或终点
* @param {*} v 有向图:终点;无向图:起点或终点
*/
function addEdges (s, v) {
/** s为起点时 */
edgeLinks[cnt] = {};
edgeLinks[cnt].to = v;
edgeLinks[cnt].next = head[s];
head[s] = cnt;
cnt += 1;
outList[s] += 1;
/** v为起点时 */
edgeLinks[cnt] = {};
edgeLinks[cnt].to = s;
edgeLinks[cnt].next = head[v];
head[v] = cnt;
cnt++;
outList[v] += 1;
}
function initHead (n) {
head = new Array(n).fill(-1);
}
function initOutList (n) {
outList = new Array(n).fill(0);
}
最后
如果有更好的解法或者思路, 欢迎在评论区和我交流~ ღ( ´・ᴗ・` )