这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战
前面我们讲过了二叉树及他的递归算法。# 二叉树递归算法题(经典算法题JavaScript递归实现,持续产出中)。本节我们继续来看LeetCode中难度为中等的这道二叉树算法题。
递归解法:
因为该题给的入参是二叉树的前序遍历,即root -> left -> right的顺序遍历的二叉树。所以我们可以递归去求当前节点的树长度,当遇到#时结束当前节点子树的长度遍历,拿到整个树的长度与题目给出的入参长度比较。
如图所示:
代码如下:
const isValidSerialization = function (preorder) {
// 判断是否数组越界,越界表示想去查下一个节点但是给定的数组中缺少该节点
let isOut = false;
const arr = preorder.split(',');
const len = getLenFunc(0, arr);
// 递归函数,表示从idx节点开始的树长度
function getLenFunc (idx, array) {
// 如果想查询的下标越界返回0,并置为数组越界,后续递归也不需要进行了
if (isOut || idx >= array.length) {
isOut = true;
return 0;
}
// 如果查询的节点是空节点,返回1
if (array[idx] === '#') return 1;
// 左子树的下标往后移动一位
const leftLen = getLenFunc(1 + idx, array);
// 右子树的起始下标为往后移动1+左子树的长度,如上图所示
const rightLen = getLenFunc(1 + idx + leftLen, array);
// 树长度为当前节点+左子树长度+右子树长度
return 1 + leftLen + rightLen;
}
return !isOut && len === arr.length;
}
结合栈解法
我们看到,一棵树遍历到叶子节点,必定是node->#->#,那如果我们把这个叶子节点当做一个空节点呢,相当于从下往上砍树,能正好砍完就说明是合理的树结构。
我们从根节点开始遍历,往一个栈中压入节点,当压入2个#时,把2个空节点连同叶子节点一起出栈,再压入一个空节点#进去,直到遍历完栈内数据为[#]表示是一个合理的树结构。
如图所示:
压入4,#,#时,#,#,4出栈,再压入#
代码如下:
const isValidSerialization = function (preorder) {
const stack = [];
const array = preorder.split(',');
for (let i = 0; i < array.length; i++) {
stack.push(array[i])
while (stack.length >= 3 && stack[stack.length - 1] === '#' && stack[stack.length - 2] === '#' && stack[stack.length-3] !== '#') {
stack.pop();
stack.pop();
stack[stack.length - 1] = '#';
}
}
return stack.length === 1 && stack[0] === '#';
}
出入度
再优化一下??上个算法的空间复杂度是O(N),我们需要一个建立一个栈,我们能不能使用O(1)实现?
我们可以想到,一个普通节点有1个入度,2个出度,那我们维护一个出入度总计值diff(ps: diff初始值是1,因为当前空间需要一个出度,来塞入root节点,否则root节点没有地方塞),即当节点是数字时,-1入度,+2出度,当节点是#时,-1入度。当节点都被填满,应该出入度总额是0。
具体代码就不写了,希望大家能自己思考下,写个伪代码放到讨论区,我会积极回复的~
有更好更多的方案也请不吝赐教~
😀😀😀欢迎大家讨论,看完记得点个👍🏻哦。本文的代码,包括出入度方案,已放Github,里面还有之前算法的代码,后续代码也会陆续更新。