导航
[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式
[封装03-设计模式] Decorator 装饰器模式在前端的应用
[封装04-设计模式] Publish Subscribe 发布订阅模式在前端的应用
[封装05-ElementUI源码01] Row Col Container Header Aside Main
Footer
[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] koa
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend
[源码-vue06] Vue.nextTick 和 vm.$nextTick
[源码-react01] ReactDOM.render01
[源码-react02] 手写hook调度-useState实现
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[数据结构和算法01] 二分查找和排序
[数据结构和算法02] 回文字符串
[数据结构和算法03] 栈 和 队列
[数据结构和算法04] 链表 和 树
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[深入24] Fiber
[深入25] Typescript
[深入26] Drag
[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
[前端学java13-SpringCloud] Eureka + RestTemplate + Zuul + Ribbon
复习笔记-01
复习笔记-02
复习笔记-03
复习笔记-04
(一) 前置知识
(1) 一些单词
linkedList 链表
invert 翻转
binary tree 二叉树
balance 平衡 // balance binary tree 平衡二叉树
symmetric 对称的
average 平均值
preorder 前序遍历
inorder 中序遍历
postorder 后续遍历
traversal 遍历 n
deep first search 深度优先 ----- DFS
breath first search 广度优先 --- BFS
(二) 链表
(1) 链表的一些概念
- 链表是一组 ( 节点 ) 组成的集合
- 每个节点都是一个对象,对象的引用指向下一个节点,指向下一个节点的引用叫做 ( 链 )
节点- data 保存数据
- next 下一个节点的引用
- prev 上一个节点的引用
头节点- 因为链表头节点的确定比较麻烦,因此在最前面添加一个特殊的节点,叫做头节点
- 表示链表头部
有头节点的链表的 ( 插入 ) 操作- 有头节点的链表的插入操作,效率比较高
- 特点
插入和删除的时间复杂度- 单链表插入和删除的时间复杂度是
O(1) - 首先遍历寻找的时间复杂度
O(n),然后添加删除的时间复杂度是O(1)=>O(n)
- 单链表插入和删除的时间复杂度是
非连续性- 链表是非连续的,所以不需要一整块完整连续的内存空间
- 对比数组:数组则需要是完整的连续内存空间
- 链表对比数组
- 查找
数组查找:因为是连续的内存空间,只需要计算偏移量,效率高链表查找:因为内存空间不连续,每次都必须从头节点开始遍历通过指针找到目标节点,效率低
- 添加和删除
数组添加和删除:添加删除元素后,其余的元素要来占据删除的元素的位置,所以要移动很多节点,效率低链表查找:直接操作通过指针执行,不需要移动节点,效率高
- 查找
(2) js实现单链表
单链表的实现
---
// 合并两个有序链表
// 为什么不把 NodeConstructor 放在 LinkedList 的外面?
// 因为可以内部维护,添加和删除时关注value本身
// function NodeConstructor(value) {
// this.value = value; // 值
// this.next = null; // 指针
// }
function LinkedList() {
// NodeConstructor 构造函数,用来生成节点
function NodeConstructor(el) {
this.el = el; // 值
this.next = null; // 指针
}
this.head = null; // 单链表头节点
this.length = 0; // 单链表长度
// 1
// append 在尾部添加节点
this.append = (el) => {
const node = new NodeConstructor(el);
if (!this.head) {
this.head = node;
} else {
let current = this.head; // 这里current和this.head都指向同一个引用,对象类型可以使用const
while (current.next) {
// head节点存在,不断通过next指针找到尾节点,尾节点next指向null
current = current.next;
}
current.next = node;
}
this.length++;
};
// 2
// find
this.find = (id) => {
let currentId = 0;
let current = this.head;
while (currentId < this.size()) {
if (currentId === id) {
return current;
}
currentId++;
current = current.next;
}
};
// 3
// findIndex indexOf 寻找链表中的节点
this.findIndex = this.indexOf = (el) => {
let current = this.head; // 缓存
let currentId = 0;
// 遍历所有节点,如果找到节点,就返回节点的id,否则继续向后寻找
while (currentId < this.size()) {
if (current.el === el) return currentId;
currentId++;
current = current.next;
}
};
// 4
// insert 插入节点
// 实现插入节点逻辑首先我们要考虑边界条件,如果插入的位置在头部或者比尾部位置还大,我们就没必要从头遍历一遍处理了,这样可以提高性能
this.insert = (position, el) => {
const node = new NodeConstructor(el);
let prevNode = null;
let currentNode = this.head;
let currentId = 0;
if (position >= 0 && position <= this.length) {
if (position === 0) {
node.next = currentNode; // 插入节点的next指向头节点
this.head = node; // 再重新赋值给头节点,则更新头节点,注意头插时一定要更新头节点,一半情况下不需要更新头节点
} else {
while (currentId < position) {
currentId++;
prevNode = currentNode;
currentNode = currentNode.next;
}
// 找到位置后
node.next = currentNode;
prevNode.next = node;
this.length++; // 插入完成后,length++
return true;
}
} else {
return false; // 不在链表的范围内,简单的处理成不做处理
}
};
// 5
// removeAt
// 移除指定位置的元素
// 移除指定位置的节点也需要判断一下边界条件,可插入节点类似,但要注意移除之后一定要将链表长度-1
this.removeAt = (position) => {
// position表示位置,是number类型
// 检测边界条件
if (position >= 0 && position < this.length) {
let prevNode = null; // 缓存当前节点,也可以理解为是当前节点的前一个节点
let current = this.head; // 当前节点
let currentIndex = 0; // 当前节点位置
if (position === 0) {
this.head = current.next; // 如果删除链表的第一个节点,则直接把第二个节点赋值给第第一个节点 ( head = head.next )
} else {
// 这里没有直接判断是不是最后一个,因为没有最后一个的标志位指针,还是要从头遍历 ( 即没有做这样的判断 position === length -1 )
while (currentIndex++ < position) {
prevNode = current;
current = current.next; // 不断重头往后遍历,知道找到position所在的节点
}
prevNode.next = current.next;
// 将 ( 要删除的节点的前一个节点的next ) 指向 ( 将要删除的节点的下一个节点 )
// 这里包含了最后一个节点的删除,当是最后一个节点时,current.next === null,prevNode.next = current.next = null 表达式也是成立的
}
this.length--; // 操作完成,长度-1
return current.el; // 返回删除的节点上的数据el
} else {
return null; // 随便返回一个值,表示没做任何操作,因为位置都不在链表上
}
};
// 6
// 移除指定节点
// 移除指定节点实现非常简单,我们只需要利用之前实现好的查找节点先找到节点的位置,然后再用实现过的removeAt即可
this.remove = (el) => {
let index = this.indexOf(el);
this.removeAt(index);
};
// 7
// 判断链表是否为空
// 只需要判断 length 是否为0,返回boolean
this.isEmpty = () => {
return this.length === 0;
};
// 8
// 将链表转化为数组返回
this.toArray = () => {
let current = this.head;
let resultList = [];
while (current.next) {
// 节点的next存在,就把el添加进数组,直到最后一个节点,最后一个节点的 ( lastNde.next = null )
resultList.push(current.el);
current = current.next; // 不断 next
}
// 注意尾节点的处理
resultList.push(current.el);
return resultList;
};
this.size = () => this.length;
}
const linkedList = new LinkedList();
linkedList.append(10);
linkedList.append(20);
linkedList.append(30);
linkedList.append(40);
console.log(`linkedList.indexOf(40)`, linkedList.indexOf(40));
console.log(`linkedList.length`, linkedList.length);
linkedList.insert(2, 800);
console.log(`linkedList.length`, linkedList.length);
console.log(`linkedList.findIndex(800)`, linkedList.findIndex(800));
console.log(`linkedList.findIndex(40)`, linkedList.findIndex(40));
linkedList.remove(10);
console.log(`linkedList.find(0)`, linkedList.find(0));
const arr = linkedList.toArray();
console.log(`arr`, arr);
(3) 反转链表
// 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
// ---
// 输入: head = [1,2,3,4,5]
// 输出: [5,4,3,2,1]
// ---
// 输入:head = []
// 输出:[]
// ---
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function (head) {
if (!head || !head.next) return head; // 边界
let [pre, cur] = [null, head];
// pre 用来标记反转后的头节点
// cur 当前正序的处理的节点
while (cur) {
const temp = cur.next; // 用来缓存当前正序的下一个节点,因为之后会去修改cur,就不知道下一个节点是指向谁了,所以先缓存起来
cur.next = pre; // 反转链表,指向反转链表的头节点
// 以下是反转链表后的处理
// 2022-09-29更新 --- 即 ( 往后移动指针 ),进行下一次遍历
pre = cur; // 反转链表的头节点就是cur
cur = temp; // 正序的链表指针向后移,进行下一轮处理
}
return pre; // 返回反转后的链表头节点
};
2022.04.06 - 反转链表
---
function reverseLinkedList(head) {
if (head === null) return null; // 边界
let [prev, current] = [null, head]; // 头节点的上一个节点是null,current为head节点
while (current) {
const nextTemp = current.next; // 缓存当前节点的下一个节点,因为下面会修改 current.next,就无法找到正序遍历时的下一个节点
current.next = prev; // 反转指针指向
// (反转后的处理) - 修改指针指向后的处理,为下一次循环做准备
// 其实就是移动指针,往后移动
prev = current; // 将当前遍历的节点的上一个元素,指向当前节点 ----- 即遍历后移
current = nextTemp; // 将当前遍历的元素,重新设置为下一个元素 --- 即遍历后移
}
return prev;
}
(4) 合并两个有序链表
// 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
// 输入:l1 = [1,2,4], l2 = [1,3,4]
// 输出:[1,1,2,3,4,4]
// ---
// 输入:l1 = [], l2 = []
// 输出:[]
// ---
// 输入:l1 = [], l2 = [0]
// 输出:[0]
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
var mergeTwoLists = function (list1, list2) {
if (list1 === null) return list2; // 如果其中一个为空,就直接返回另一个链表
if (list2 === null) return list1;
let list3 = new ListNode(null); // 先生成一个空节点,val=null next=null
let cacheList3Head = list3;
// 这里缓存一下list3的头节点,该头节点是我们自己定义的头节点,和我们最终返回的头节点不一样
// 问题:为什么要缓存头节点呢?
// 回答:因为list我们在不断的重新赋值next,那么执行后指针不是在头节点了
while (list1 && list2) {
// 遍历两个链表,直到其中一个为空
if (list1.val < list2.val) {
// 比较两个链表中的最前面的节点,取小的添加给list链表,然后移动list的next指针到尾节点,同时移动小的链表的指针next,继续比较list.next.val 和 list2.val
// 问题:为什么要移动 list3
// 回答:因为list3中还要添加新的节点进来,必须在尾节点上的 next 指向新的节点
list3.next = list1;
list3 = list3.next;
list1 = list1.next;
} else {
list3.next = list2;
list3 = list3.next;
list2 = list2.next;
}
}
// 当上面的整个while循环结束,表示其中一个链表已经遍历完了,继续遍历有剩余节点的链表,添加给list3
// while (list1) {
// list3.next = list1;
// list3 = list3.next;
// list1 = list1.next;
// }
// while (list2) {
// list3.next = list2;
// list3 = list3.next;
// list2 = list2.next;
// }
// 2022-09-29 优化如下
if (list1) {
list3.next = list1;
}
if (list2) {
list3.next = list2;
}
return cacheList3Head.next;
// 最后返回新节点的头节点,不然我们初始化list3的时的头节点并不是答案中的头节点,把我们自己的头节点的next才是真正的返回的合并后的头节点
};
(三) 树
(1) 树的一些概念
- 二叉树是一种非线性结构,二叉树是递归定义的,其结点有左右子树之分
- 二叉树通常采用链式存储结构
- 节点
- 和链表中的节点一样,其实就是一个对象,具有值和指针
度- 指的是 ( 节点拥有的子树的数量 )
- 叶子节点
- 指的是:度为0的节点,即没有子节点的节点
- 高度
- 高度 = 深度 = 层数
- 二叉树
满二叉树- 深度为k,具有 2 ^ k - 1 个节点的树,叫做满二叉树
- 特点
- 深度和节点的关系:深度为k,具有 2 ^ k - 1 个节点
- 第i层的节点数:2 ^ (i - 1) 个节点
完全二叉树- 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树
- 特点
- 相对于满二叉树,完全二叉树的最后一层不完成,并且是右边部分缺失
平衡二叉树- 每个节点的 ( 左右子树 ) 的高度差的绝对值不超过 1
二叉搜索树-
- 节点的 左子树 只包含 小于 当前节点的数
-
- 节点的 右子树 只包含 大于 当前节点的数
-
- 所有 左子树和右子树 自身必须也是二叉搜索树
-
- 遍历
深度优先 - DFS- 前序遍历(先根遍历):
根-> 左子树 -> 右子树 - 中序遍历(中根遍历):左子树 ->
根-> 右子树 - 后续遍历(后根遍历):左子树 -> 右子树 ->
根 - 一般用递归
- 前序遍历(先根遍历):
广度优先 - BFS- 一般用队列
(2) 翻转二叉树
// 翻转一棵二叉树
// 前
// 4
// / \
// 2 7
// / \ / \
// 1 3 6 9
// 后
// 4
// / \
// 7 2
// / \ / \
// 9 6 3 1
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function (root) {
reverse(root);
function reverse(root) {
if (!root) return; // 递归结束条件
[root.left, root.right] = [root.right, root.left]; // 递归交换左右子树
reverse(root.left); // 递归
reverse(root.right);
}
return root;
};
(3) 中序遍历二叉树
// 中序遍历二叉树 - 递归
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function (root) {
// 中序遍历指的是:左子树 - 根 - 右子树
// 递归实现
const result = [];
inorderTraversal(root);
// 问题:这里为什么要再声明一个函数,而不直接使用inorderTraversal
// 回答:因为要使 result 不被重复声明,类似充当一个闭包变量或者全局变量
function inorderTraversal(root) {
if (root) {
inorderTraversal(root.left); // 左子树的递归
result.push(root.val); // 根节点,最左边的叶子节点最先进入数组
inorderTraversal(root.right); // 右子树的递归
}
}
return result;
};
(4) 前序遍历 - 递归的方法和中序差不多
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root) {
const result = []
function preorderTraversal(root) {
if (root) {
result.push(root.val)
preorderTraversal(root.left)
preorderTraversal(root.right)
}
}
preorderTraversal(root)
return result
};
(5) 二叉树的最大深度 - 递归
// 求二叉树的最大深度
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
**/
// ---------
// 3
// / \
// 9 20
// / \
// 15 7
// 返回它的最大深度 3
// 思路:深度是 左子树深度 和 右子树 深度的较大值,然后递归
// ---------
// 【1】
// recursive(root.left)
// --> 9
// --> return Math.max(recursive(root.left), recursive(root.right)) + 1
// --> return Math.max(0, 0) + 1
// --> 1
// 【2】
// recursive(root.right)
// 20
// --> left -> 15 -> 1
// --> right -> 7 -> 1
// --> Math.max(recursive(root.left), recursive(root.right)) + 1 --> Math.max(1,1) + 1 --> 2
// 【3】
// Math.max(recursive(root.left), recursive(root.right)) + 1 --> Math.max(1, 2) + 1 --> 3
var maxDepth = function (root) {
function recursive(root) {
if (!root) return 0; // 递归结束的条件,返回值
return Math.max(recursive(root.left), recursive(root.right)) + 1; // 递归的返回值
}
return recursive(root);
};
(6) 判断是否是平衡二叉树
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
// getDeep
// - 获取二叉树的最大高度
// - 左右子树高度的较大值,递归回溯
function getDeep(root) {
if (!root) return 0;
const leftDeep = getDeep(root.left);
const rightDeep = getDeep(root.right);
return Math.max(leftDeep, rightDeep) + 1;
}
// isBalanced
// - 是否是平衡二叉树
var isBalanced = function (root) {
if (!root) return true; // ------- 递归结束的条件,是平衡二叉树
const leftHeight = getDeep(root.left); // 参数节点的左子树的高度
const rightHeight = getDeep(root.right); // 参数节点的右子树的高度
const diff = Math.abs(leftHeight - rightHeight); // 左右子树的高度差的绝对值
if (diff > 1) return false; // --- 左右子树高度差绝对值大于1,就不是平衡二叉树
return isBalanced(root.left) && isBalanced(root.right); // 递归判断左右子树是否是平衡二叉树
};
(7) 二叉树是否对称
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
// 1
// / \
// 2 2
// / \ / \
// 3 4 4 3
var isSymmetric = function (root) {
if (!root) return true;
function check(left, right) {
if (!left && !right) return true; // 比较的两个节点不存在 -> 对称
if (!left || !right) return false; // 比较的两个节点,一边存在,一边不存在 -> 非对称
if (left.val !== right.val) return false; // 比较的两个节点,不等 -> 非对称
return check(left.left, right.right) && check(left.right, right.left);
// ( 左子树的左节点 和 右子树的右节点 相等 ) 并且 ( 左子树的右节点 和 右子树的左节点 相等) -> 对称
}
return check(root.left, root.right);
};
(8) 合并两颗树
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root1
* @param {TreeNode} root2
* @return {TreeNode}
*/
var mergeTrees = function (root1, root2) {
function merge(root1, root2) {
if (!root1) return root2;
if (!root2) return root1;
// 我们以root1作为基准,合并后,返回合并后的root1
root1.val = root1.val + root2.val;
root1.left = merge(root1.left, root2.left); //递归
root1.right = merge(root1.right, root2.right);
return root1;
}
return merge(root1, root2);
};
(9) 二叉搜索树中的搜索
// 题目 二叉搜索树中的搜索
// 二叉搜索树的定义
// - 1. 节点的 左子树 只包含 小于 当前节点的数
// - 2. 节点的 右子树 只包含 大于 当前节点的数
// - 3. 所有 左子树和右子树 自身必须也是二叉搜索树
// 思路
// - 要查找的值 和 树的root节点比较,大于在right中递归查找,小于在left中递归查找,等于直接返回
// - 递归结束的条件是
// - !root 不存在
// - 或者root和要查找的值相等
// 示例
// 给定二叉搜索树:
// 4
// / \
// 2 7
// / \
// 1 3
// 和值: 2
// 应该返回如下子树:
// 2
// / \
// 1 3
// 如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} val
* @return {TreeNode}
*/
var searchBST = function (root, val) {
function search(root, val) {
// 递归结束的两个条件
if (!root) return null;
if (val === root.val) return root;
return val > root.val
? search(root.right, val)
: search(root.left, val);
}
return search(root, val);
};
(10) 二叉搜索树中两个节点之和
给定一个二叉搜索树的 **根节点** `root` 和一个整数 `k` , 请判断该二叉搜索树中是否存在两个节点它们的值之和等于 `k`
假设二叉搜索树中节点的值均唯一。
---
输入: root = [8,6,10,5,7,9,11], k = 12
输出: true
解释: 节点 5 和节点 7 之和等于 12
---
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} k
* @return {boolean}
*/
var findTarget = function(root, k) {
// 1. 先遍历
const res = [] // 将树转成数组
function traversal(root) {
if (!root) return
traversal(root.left)
res.push(root.val)
traversal(root.right)
}
traversal(root)
// 2. 数组中寻在结果
for(let i = 0; i < res.length; i++) {
const dis = k - res[i];
if (res.includes(dis) && dis !== res[i]) return true
}
return false
};
(11)二叉树的层序遍历
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
if (!root) return []
// queue -> 我们用队列 queue 去维护节点的存取
// result -> 结果多维数组
const queue = [root], result = []
while(queue.length) {
const temp = [] // 收集每一层的result,然后最终都push到result中去
const queueLength = queue.length
// queueLength
// - 注意这里必须缓存queue.length
// - 如果写成 for(let i = 0; i < queue.length; i++) 是不行的,因为 queue.length 在动态变化
for(let i = 0; i < queueLength; i++) {
const current = queue.shift() // 队列,先进先出,push入队,shift出队
temp.push(current.val)
if (current.left) queue.push(current.left);
if (current.right) queue.push(current.right);
}
temp.length && result.push(temp) // temp数组有成员时,就将temp数组添加到result中去
}
return result
};
(12) 二叉搜索树的第k大节点
// 题目
// - 二叉搜索树的第k大节点
// - 给定一棵二叉搜索树,请找出其中第 k 大的节点的值
// 示例
// ---
// 输入: root = [3,1,4,null,2], k = 1
// 3
// / \
// 1 4
// \
// 2
// 输出: 4
// ---
// 输入: root = [5,3,6,2,4,null,null,1], k = 3
// 5
// / \
// 3 6
// / \
// 2 4
// /
// 1
// 输出: 4
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {number} k
* @return {number}
*/
var kthLargest = function (root, k) {
// 先遍历
const res = [];
function traversal(root) {
if (!root) return;
res.push(root.val);
traversal(root.left);
traversal(root.right);
}
traversal(root);
// 再排序,倒序
res.sort((a, b) => b - a);
// 再查找
const result = res.find((val, index) => {
return index === k - 1;
});
return result;
};
(13) 二叉树的层平均值
// 题目
// - 二叉树的层平均值
// - Average of Levels in Binary Tree
// - 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
// 思路
// - 和 BFS 遍历的大概思路相同,唯一不同的时收到到的层成员数组是平和的平均值
// 英语
// - average 平均值
// 示例
// 输入:
// 3
// / \
// 9 20
// / \
// 15 7
// 输出:[3, 14.5, 11]
// 解释:
// 第 0 层的平均值是 3 , 第1层是 14.5 , 第2层是 11 。因此返回 [3, 14.5, 11]
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var averageOfLevels = function (root) {
// 先 BSF,在bfs的过程中,搜集每一层的成员数组,然后求平均
if (!root) return [];
const queue = [root];
const result = [];
while (queue.length) {
const temp = []; // 没层的节点组成的数组
const len = queue.length;
for (let i = 0; i < len; i++) {
const current = queue.shift();
temp.push(current.val);
if (current.left) {
queue.push(current.left);
}
if (current.right) {
queue.push(current.right);
}
}
// 下面是和 BFS 广度优先遍历不同的点
if (temp.length) {
const a = temp.reduce((a, b) => a + b) / temp.length; // 这里是否要保留小数,可以 number.toFixed(2) 保留两位有效数字
result.push(a);
}
}
return result;
};
(14) 相同的树
- 对比对称树的判断,两者类似
// 题目
// - 相同的树
// - 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同
// - 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的
// 对比
// 相同树 和 对称树 的判断类似
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} p
* @param {TreeNode} q
* @return {boolean}
*/
var isSameTree = function (p, q) {
// 递归判断
function traversal(p, q) {
if (!p && !q) return true; // 保证此条件在最前面,表示如果直到不能遍历了还是相等的,必然相等
if (!p || !q) return false; // 结构不一样,必然不相等
if (p.val !== q.val) return false; // 每个节点值不一样,必然不相等
return traversal(p.left, q.left) && traversal(p.right, q.right); // 保证左子树和右子树同时为true才为true
}
return traversal(p, q);
};
资料
- js实现链表 juejin.cn/post/684490…
- 合并两个有序链表为新的链表 juejin.cn/post/684490…
- 反转链表 www.bilibili.com/video/BV19b…