前端leetcde算法面试套路之二叉树
前端就该用JS写算法 -- 树 -- 简单的那 30%
这里优先选择了 LeetCode 热题 HOT 100 中的树题,毕竟刷题的边际收益就是冲击需要算法的面试,所以 Hot 优先级更高。
二叉树的遍历
递归遍历
- 递归的时候前中后序都能直接处理完了
- 递归是前中后序遍历最简单也是最容易出理解的方法,不懂的画个图就好了
迭代遍历 -- 双色标记法
- 使用颜色标记节点状态,新节点为白色,已经访问的节点为灰色 -- 可以用数字或者其他任意标签标示
- 如果遇到的节点是白色,则标记为灰色,然后将右节点,自身,左节点一次入栈 -- 中序遍历
- 如果遇到的节点是灰色的,则将节点输出
- 注意这里是用 stack 栈来存储的,所以是后进先出,所以如果是中序遍历,左 - 中 - 右 ,那么在插入栈的时候要反过来 右 - 中 - 左
按照那个男人的指示,正常我们就用递归做就好,就好像我们做非排序题排序的时候,sort 一下就好了,但是一旦面试官问到用另外的迭代方式的时候,我们再套个模板,会比记住多个迭代写法要简单,毕竟内存容量有限,而后续遍历的迭代写法确实挺坑的,能省一点内存就省一点吧
144. 二叉树的前序遍历
// 144. 二叉树的前序遍历
/** * @分析 -- 递归 */
var preorderTraversal = function (root) {
const ret = [];
const recursion = (root) => {
if (!root) return;
ret.push(root.val);
recursion(root.left);
recursion(root.right);
};
recursion(root);
return ret;
};
/** * @分析 -- 迭代 -- 双色标记法 * 1. 使用颜色标记节点状态,新节点为白色,已经访问的节点为灰色 -- 可以用数字或者其他任意标签标示 * 2. 如果遇到的节点是白色,则标记为灰色,然后将右节点,自身,左节点一次入栈 -- 中序遍历 * 3. 如果遇到的节点是灰色的,则将节点输出 * 4. 注意这里是用 stack 栈来存储的,所以是后进先出,这里是前序遍历,中 - 左 - 右 ,那么在插入栈的时候要反过来 右 - 左 - 中 */
var preorderTraversal = function (root) {
const ret = [];
const stack = [];
stack.push([root, 0]); // 0 是白色未处理的,1 是灰色处理过的
while (stack.length) {
const [root, color] = stack.pop();
if (root) {
if (color === 0) {
// 遇到白球,则插入 -- 前序
stack.push([root.right, 0]);
stack.push([root.left, 0]);
stack.push([root, 1]);
} else {
// 遇到灰球,则收网
ret.push(root.val);
}
}
}
return ret;
};
1.94 二叉树的中序遍历
// 94. 二叉树的中序遍历
/** * @分析 * 1. 递归的时候前中后序都能直接处理完了 * 2. 递归是前中后序遍历最简单也是最容易出理解的方法,不懂的画个图就好了 */
var inorderTraversal = function(root) {
const ret = []
const recursion = root => {
if(!root) return
recursion(root.left)
// 这里是中序,所以在两个递归之间,如果是前序就在前面,后序就在后面
ret.push(root.val)
recursion(root.right)
}
recursion(root)
return ret
};
/** * @分析 -- 迭代 -- 双色标记法 * 1. 使用颜色标记节点状态,新节点为白色,已经访问的节点为灰色 -- 可以用数字或者其他任意标签标示 * 2. 如果遇到的节点是白色,则标记为灰色,然后将右节点,自身,左节点一次入栈 -- 中序遍历 * 3. 如果遇到的节点是灰色的,则将节点输出 * 4. 注意这里是用 stack 栈来存储的,所以是后进先出,所以如果是中序遍历,左 - 中 - 右 ,那么在插入栈的时候要反过来 右 - 中 - 左 */
var inorderTraversal = function(root) {
const ret = []
const stack = []
stack.push([root,0]) // 0 是白色未处理的,1 是灰色处理过的
while(stack.length) {
const [root,color] = stack.pop()
if(root){
if(color === 0){
// 遇到白球,则插入 -- 中序遍历
stack.push([root.right,0])
stack.push([root,1])
stack.push([root.left,0])
}else{
// 遇到灰球,则收网
ret.push(root.val)
}
}
}
return ret
};
145. 二叉树的后序遍历
// 145. 二叉树的后序遍历
/** * @分析 -- 递归 */
var postorderTraversal = function(root) {
const ret = []
const dfs = (root) => {
if(!root) return
dfs(root.left)
dfs(root.right)
ret.push(root.val)
}
dfs(root)
return ret
};
/** * @分析 -- 迭代 -- 双色球 */
var postorderTraversal = function(root) {
const ret = []
const stack = []
stack.push([root,0])
while(stack.length){
const [root,color] = stack.pop()
if(root) {
if(color === 0){
stack.push([root,1])
stack.push([root.right,0])
stack.push([root.left,0])
}else{
ret.push(root.val)
}
}
}
return ret
}
参考视频:传送门
101. 对称二叉树
分析
- 对称二叉树,其实是要求是否镜像对齐,所以递归过程至少需要两个根节点,然后 dfs 主要就是判断是否是对称的两棵树
- 这里是自顶向下分配相互比较的子树节点 left 和 right,然后再自底向上的返回最终结果
- 在某一次dfs中,如果比较双方都是 null,那么证明比较双方是对称的;如果出现只有一方有值,或者双方有值但是值不一样的时候,返回false;
- 每次递归都是左右外层构成比较,左右内层构成比较
- 时间复杂度: O(h), 其中 h 是树的高度
// 101. 对称二叉树
var isSymmetric = function (root) {
if (!root) return false;
const dfs = (left, right) => {
if (!left && !right) return true;
if (!left || !right || left.val !== right.val) return false;
return dfs(left.left, right.right) && dfs(left.right, right.left);
};
return dfs(root.left, root.right);
};
104. 二叉树的最大深度
- 使用树的三种搜索方式,层序,自顶向下的dfs,自底向上的递归dfs
层序遍历
- 无论是深度,层数等,直接用层序遍历找到最后一层的最后一个叶子节点即可
- 时间复杂度 O(N), 空间复杂度 O(K) -- K 是最大宽度
// 104. 二叉树的最大深度
/** * 1.无论是深度,层数等,直接用层序遍历找到最后一层的最后一个叶子节点即可 */
var maxDepth = function(root) {
if(!root) return 0
let ret = 0
const queue = []
queue.push(root)
while(queue.length){
ret++ // 进入一层
let len = queue.length
while(len--){
// 层序遍历
const root = queue.shift()
if(root.left) queue.push(root.left)
if(root.right) queue.push(root.right)
}
}
return ret
}
dfs -- 自顶向下
- 我们在计算层数的时候,可以考虑到,没遍历一层,就携带一个参数,这个参数是一个标记,比方这里就是深度 depth
- 这样当我们遍历到叶子节点的时候,都可以和最大值比对一下,然后结束这一条路线
- 时间复杂度 O(N), 空间复杂度 O(D) -- D 是深度
/** * 1. 自顶向上,带个层数参数,判定为叶子节点就进行最大值判断 */
var maxDepth = function (root) {
if(!root) return 0
let ret = 0;
const dfs = (root, depth) => {
if (root.left) dfs(root.left, depth + 1);
if (root.right) dfs(root.right, depth + 1);
// 走到这的时候,证明是叶子节点了,所以取最大值,就结束这一次的
ret = Math.max(ret, depth);
};
dfs(root, 1);
return ret;
};
递归 -- 自低向上
- 既然有自顶向下,那么当然就有自低向上了;
- 就我浅薄的算法能力而已,自顶向下就是带参数的深度优先遍历 DFS, 而自低向上,是递归,需要dfs 到了底部,然后归到根节点,所以这里用的是 recursion 作为方法名。
- 自顶向下是从根节点开始算一层深度,然后跑到叶子节点结束;自低向上反过来,跑到最底层,然后不断求叶子结点的最大深度,然加上自身返回到上层
- 时间复杂度 O(N), 空间复杂度 O(1)
// 自低向上
var maxDepth = function (root) {
const recursion = (root) => {
// 只是到了底部,所以高度为 0
if (!root) return 0;
// 每一个节点的高度是多少,就是两个节点树的最大高度+自己所处的这一层1
return Math.max(recursion(root.left), recursion(root.right))+1;
};
return recursion(root);
};
226. 翻转二叉树
自底向上
- 因为要求的是反转二叉树,对于任意一颗子树,其实都是要做一样的操作,所以可以先递到叶子节点,然后开始进行翻转
- 自底向上将翻转好的子树传递到上层的节点,直到最后的根节点,得到了两课翻转好的树,然后交换一下一下位置就好了
- 时间复杂度 O(N)
// 226. 翻转二叉树
var invertTree = function (root) {
const dfs = (root) => {
// 到达了最底部,直接返回 null
if (!root) return null;
// 1.递归获取翻转后的左右子树
const left = dfs(root.left)
const right = dfs(root.right)
// 2.反转两棵树的位置
root.left = right
root.right = left
// 最后返回这个反转之后的树
return root;
};
return dfs(root);
};
推荐阅读
前端工程师leetcode算法面试必备-二分搜索算法(下)
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
js2030code阅读 74
正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...
寒青赞 56阅读 8.5k评论 11
JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...
jenemy赞 49阅读 7.1k评论 12
从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...
乌柏木赞 75阅读 7.1k评论 16
再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...
libinfs赞 42阅读 6.8k评论 12
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...
乌柏木赞 45阅读 8.6k评论 6
从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...
乌柏木赞 35阅读 6.7k评论 10
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。