一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 题目 |
三 解题思路 |
四 统计分析 |
五 解题套路 |
二 题目
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
限制:
0 <= 节点个数 <= 5000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {number[]} preorder
* @param {number[]} inorder
* @return {TreeNode}
*/
const buildTree = (preorder, inorder) => {
}
根据上面的已知函数,小伙伴们可以先尝试破解本题,确定了自己的答案后再看下面代码。
三 解题思路
拿到题目,咱们先了解前序遍历和中序遍历:
前序遍历:根节点、左子树、右子树。每个子树的遍历顺序同样满足前序遍历顺序。
中序遍历:左子树、根节点、右子树。每个子树的遍历顺序同样满足中序遍历顺序。
OK,这时候需要先声明一下:
没做过树的简单题目的小伙伴可以先行撤退了,这道题是中等难度题目。
咱们还是使用两种方法:
递归和迭代
解法一:递归
/**
* @name buildTree
* @description 重建二叉树
* @param {array[number]} preorder 先序遍历数组
* @param {array[number]} inorder 中序遍历数组
*/
const buildTree = (preorder, inorder) => {
// 如果当前先序遍历数组没有长度,说明里面没有节点了,返回 null
if (!preorder.length) {
return null;
}
// 如果先序遍历的数组有内容,那么当前跟节点肯定是它的第 1 个元素
const rootValue = preorder[0];
const root = {
val: preorder[0],
left: null,
right: null,
};
// 获取到当前元素(第 1 个元素)的位置
const rootInIndex = inorder.indexOf(rootValue);
// 裁剪左边列表
const leftPre = preorder.slice(1, rootInIndex + 1);
const leftIn = inorder.slice(0, rootInIndex);
// 裁剪右边列表
const rightPre = preorder.slice(rootInIndex + 1);
const rightIn = inorder.slice(rootInIndex + 1);
// 进一步递归判断
root.left = buildTree(leftPre, leftIn);
root.right = buildTree(rightPre, rightIn);
// 返回节点
return root;
};
const preorder = [3, 9, 20, 15, 7];
const inorder = [9, 3, 15, 20, 7];
console.log(buildTree(preorder, inorder));
递归的方式可能比较容易讲解。
先给代码注入灵魂:
console.log('------');
console.log(`此时的值是:${rootValue}`);
console.log(`它中序遍历中的索引:${rootInIndex}`);
console.log(`它在先序的左部位:${leftPre}`);
console.log(`它在中序的左部位:${leftIn}`);
console.log(`它在先序的右部位:${rightPre}`);
console.log(`它在中序的右部位:${rightIn}`);
然后查看它们的输出:
------
此时的值是:3
它中序遍历中的索引:1
它在先序的左部位:9
它在中序的左部位:9
它在先序的右部位:20,15,7
它在中序的右部位:15,20,7
------
此时的值是:9
它中序遍历中的索引:0
它在先序的左部位:
它在中序的左部位:
它在先序的右部位:
它在中序的右部位:
------
此时的值是:20
它中序遍历中的索引:1
它在先序的左部位:15
它在中序的左部位:15
它在先序的右部位:7
它在中序的右部位:7
------
此时的值是:15
它中序遍历中的索引:0
它在先序的左部位:
它在中序的左部位:
它在先序的右部位:
它在中序的右部位:
------
此时的值是:7
它中序遍历中的索引:0
它在先序的左部位:
它在中序的左部位:
它在先序的右部位:
它在中序的右部位:
可以看到,我们通过将先序和中序数组逐步拆解的方式,将它们逐步给分解下去。
当它们的左右部位为空的时候,触发 if (!preorder.length) { }
返回 null
。
最终拼凑成一块。
这里 jsliang 的描述可能不够完善,小伙伴如果不太熟悉可以打印下代码进一步了解。
如果你很感兴趣,非常想懂为何如此,欢迎加 jsliang 的微信进一步探讨。GitHub:https://github.com/LiangJunrong/document-library
迭代
const buildTree = (preorder, inorder) => {
if (!preorder.length) {
return null;
}
const root = {
val: preorder[0],
left: null,
right: null,
};
const stack = [];
stack.push(root);
let inorderIndex = 0;
for (let i = 1; i < preorder.length; i++) {
let node = stack[stack.length - 1];
if (node.val !== inorder[inorderIndex]) {
node.left = {
val: preorder[i],
left: null,
right: null,
};
stack.push(node.left);
} else {
while (stack.length && stack[stack.length - 1].val === inorder[inorderIndex]) {
node = stack.pop();
inorderIndex++;
}
node.right = {
val: preorder[i],
left: null,
right: null,
};
stack.push(node.right);
}
}
return root;
};
const preorder = [3, 9, 20, 15, 7];
const inorder = [9, 3, 15, 20, 7];
console.log(buildTree(preorder, inorder));
相对而言,迭代的代码是我直接抄官方解答的,如果小伙伴了解了递归,那么迭代的方法也差不多可以了解,这里就不哆嗦了。
相对而言,发现中等难度的题目的确不容易讲解,个人感觉这次砸锅了,讲得不够通透,后续我写小册的时候应该会配合 GIF 图等演示。
四 统计分析
解法 | 执行用时 / 击败率 | 内存消耗 / 击败率 |
---|---|---|
解法 1 | 148 ms / 35.06%% | 127.2 MB / 100.00% |
解法 1 | 88 ms / 91.00% | 36.3 MB / 100.00% |
五 套路分析
本题套路为:在对付树和链表的遍历上,我们优先采取递归和迭代的解题方式,争取尽可能快地破解,然后根据他人的内容进一步补充完善自己。
该套路后面会更新到 LeetCode 目录下的刷题技巧上,小伙伴可以通过 jsliang 的仓库找到。
路径:document-library/other-library/LeetCode/刷题技巧
如果小伙伴有更好的思路想法,或者没看懂其中某种解法,欢迎评论留言或者私聊 jsliang~
不折腾的前端,和咸鱼有什么区别!
jsliang 会每天更新一道 LeetCode 题解,从而帮助小伙伴们夯实原生 JS 基础,了解与学习算法与数据结构。
浪子神剑 会每天更新面试题,以面试题为驱动来带动大家学习,坚持每天学习与思考,每天进步一点!
扫描上方二维码,关注 jsliang 的公众号(左)和 浪子神剑 的公众号(右),让我们一起折腾!
jsliang 的文档库 由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于https://github.com/LiangJunrong/document-library上的作品创作。
本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。