├── README.md ├── images ├── offer.png ├── 下一节点.gif ├── 二叉树的子结构.gif ├── 二叉树的深度.gif ├── 二叉树的镜像.gif ├── 判断二叉树的后续遍历.gif ├── 前序遍历.gif ├── 对称二叉树.gif ├── 求第k大节点.gif └── 路径和.gif ├── 两个栈实现队列.md ├── 二叉搜索树的后续遍历序列.md ├── 二叉树中和为某一值的路径.md ├── 二叉树搜索第 K 大结点.md ├── 二叉树的下一节点.md ├── 二叉树的深度.md ├── 二叉树的镜像.md ├── 二维数组中的查找.md ├── 从上打印二叉树.md ├── 剑指 offer 二叉树总结.md ├── 剑指 offer 字符串的总结.md ├── 剑指 offer 数组总结.md ├── 包含 min 函数的栈.md ├── 反转字符串.md ├── 在排序数组中查找数字.md ├── 字符串的排列.md ├── 对称的二叉树.md ├── 序列化二叉树和反序列化二叉树.md ├── 数组中出现次数超过一半的数字.md ├── 数组中的逆序对.md ├── 数组中重复的数字.md ├── 旋转数组中的最小数字.md ├── 替换空格.md ├── 最长不含重复字符的子字符串.md ├── 树的子结构.md ├── 第一个只出现一次的字符串.md ├── 表示数值的字符串.md ├── 调整数组的顺序使其奇数位于偶数的前面.md ├── 连续子数组的最大和.md └── 重建二叉树.md /README.md: -------------------------------------------------------------------------------- 1 | ![](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/offer.png) 2 | 3 | # 写在前边 4 | 5 | > 2019/8/26 ,每天整理一道剑指 offer 题,锻炼思维逻辑能力以及编码能力,用心去记录每一道,生活才不会那么枯燥无味。 6 | 7 | #### 1、练习“站桩”每天要坚持,一天都不能少! 8 | 9 | #### 2、练“武”不练“功”,到老一场空! 10 | 11 | #### 3、内练心智,外练筋骨! 12 | 13 | #### 4、再好的功夫,练不好基本功,也会走样! 14 | 15 | #### 5、练”武“太急是大忌,”基本功“要稳!! 16 | 17 | | 序号 | 题目类型 | 题目 | 动画实现 | 代码 | 18 | | ---- | -------------- | ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 19 | | 1 | 二叉树 | 重建二叉树 | ![https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86.gif](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/前序遍历.gif) | [题目详解](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E9%87%8D%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 20 | | 2 | 二叉树 | 二叉树的下一节点 | ![](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/%E4%B8%8B%E4%B8%80%E8%8A%82%E7%82%B9.gif?raw=true) | [题目详解](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E4%B8%8B%E4%B8%80%E8%8A%82%E7%82%B9.md) | 21 | | 3 | 二叉树 | 树的子结构 | ![https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%AD%90%E7%BB%93%E6%9E%84.gif](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/二叉树的子结构.gif) | [题目详解](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E6%A0%91%E7%9A%84%E5%AD%90%E7%BB%93%E6%9E%84.md) | 22 | | 4 | 二叉树 | 二叉树的镜像 | ![https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%95%9C%E5%83%8F.gif](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/二叉树的镜像.gif) | [题目详解](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%95%9C%E5%83%8F.md) | 23 | | 5 | 二叉树 | 对称二叉树 | ![https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/%E5%AF%B9%E7%A7%B0%E4%BA%8C%E5%8F%89%E6%A0%91.gif](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/对称二叉树.gif) | [题目详解](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%AF%B9%E7%A7%B0%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 24 | | 6 | 二叉树 | 从上到下打印二叉树 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E4%BB%8E%E4%B8%8A%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 25 | | 7 | 二叉树 | 二叉搜索树的后序遍历序列 | ![https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/%E5%88%A4%E6%96%AD%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E7%BB%AD%E9%81%8D%E5%8E%86.gif](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/判断二叉树的后续遍历.gif) | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E5%90%8E%E7%BB%AD%E9%81%8D%E5%8E%86%E5%BA%8F%E5%88%97.md) | 26 | | 8 | 二叉树 | 二叉树中和为某一值的路径 | ![https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/%E8%B7%AF%E5%BE%84%E5%92%8C.gif](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/路径和.gif) | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%92%8C%E4%B8%BA%E6%9F%90%E4%B8%80%E5%80%BC%E7%9A%84%E8%B7%AF%E5%BE%84.md) | 27 | | 9 | 二叉树 | 二叉搜索树的第 K 大节点 | ![https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/%E6%B1%82%E7%AC%ACk%E5%A4%A7%E8%8A%82%E7%82%B9.gif](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/求第k大节点.gif) | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E4%BA%8C%E5%8F%89%E6%A0%91%E6%90%9C%E7%B4%A2%E7%AC%AC%20K%20%E5%A4%A7%E7%BB%93%E7%82%B9.md) | 28 | | 10 | 二叉树 | 二叉树序列化和反序列化 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%BA%8F%E5%88%97%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%BA%8C%E5%8F%89%E6%A0%91.md) | 29 | | 11 | 二叉树 | 二叉树的深度 | ![https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%B7%B1%E5%BA%A6.gif](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/images/二叉树的深度.gif) | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%B7%B1%E5%BA%A6.md) | 30 | | 12 | 二叉树题型总结 | 二叉树小结 | | [小结](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%89%91%E6%8C%87%20offer%20%E4%BA%8C%E5%8F%89%E6%A0%91%E6%80%BB%E7%BB%93.md) | 31 | | 13 | 数组 | 数组中重复的数字 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97.md) | 32 | | 14 | 数组 | 二维数组中的查找 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9F%A5%E6%89%BE.md) | 33 | | 15 | 数组 | 旋转数组中的最小数字 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97.md) | 34 | | 16 | 数组 | 调整数组的顺序让所有奇数在偶数面前 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E8%B0%83%E6%95%B4%E6%95%B0%E7%BB%84%E7%9A%84%E9%A1%BA%E5%BA%8F%E4%BD%BF%E5%85%B6%E5%A5%87%E6%95%B0%E4%BD%8D%E4%BA%8E%E5%81%B6%E6%95%B0%E7%9A%84%E5%89%8D%E9%9D%A2.md) | 35 | | 17 | 数组 | 数组中出现次数超过一半的数字 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E6%95%B0%E7%BB%84%E4%B8%AD%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E8%B6%85%E8%BF%87%E4%B8%80%E5%8D%8A%E7%9A%84%E6%95%B0%E5%AD%97.md) | 36 | | 18 | 数组 | 连续子数组的最大和 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md) | 37 | | 19 | 数组 | 在排序数组中查找数字 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E6%95%B0%E5%AD%97.md) | 38 | | 20 | 数组 | 数组小结 | | [小结](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%89%91%E6%8C%87%20offer%20%E6%95%B0%E7%BB%84%E6%80%BB%E7%BB%93.md) | 39 | | 21 | 字符串 | 替换空格 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.md) | 40 | | 22 | 字符串 | 表示数值的字符串 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E8%A1%A8%E7%A4%BA%E6%95%B0%E5%80%BC%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 41 | | 23 | 字符串 | 字符串的排列 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97.md) | 42 | | 24 | 字符串 | 最长不含重复字符的子字符串 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E6%9C%80%E9%95%BF%E4%B8%8D%E5%90%AB%E9%87%8D%E5%A4%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 43 | | 25 | 字符串 | 翻转字符串 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 44 | | 26 | 字符串 | 第一次只出现一次的字符 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/第一个只出现一次的字符串.md) | 45 | | 27 | 栈/队列 | 两个栈实现一个队列 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E4%B8%A4%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md) | 46 | | 28 | 栈/队列 | 包含 min 函数的栈 | | [题目解析](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%8C%85%E5%90%AB%20min%20%E5%87%BD%E6%95%B0%E7%9A%84%E6%A0%88.md) | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /images/offer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/offer.png -------------------------------------------------------------------------------- /images/下一节点.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/下一节点.gif -------------------------------------------------------------------------------- /images/二叉树的子结构.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/二叉树的子结构.gif -------------------------------------------------------------------------------- /images/二叉树的深度.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/二叉树的深度.gif -------------------------------------------------------------------------------- /images/二叉树的镜像.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/二叉树的镜像.gif -------------------------------------------------------------------------------- /images/判断二叉树的后续遍历.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/判断二叉树的后续遍历.gif -------------------------------------------------------------------------------- /images/前序遍历.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/前序遍历.gif -------------------------------------------------------------------------------- /images/对称二叉树.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/对称二叉树.gif -------------------------------------------------------------------------------- /images/求第k大节点.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/求第k大节点.gif -------------------------------------------------------------------------------- /images/路径和.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luxiangqiang/JianZhi-Offer_JavaScript/f63077b4d0fc2feae6132ee30800b6d8420b0bfa/images/路径和.gif -------------------------------------------------------------------------------- /两个栈实现队列.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/10/24 3 | Title: 用两个栈实现队列 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ### 面试题9:用两个栈实现队里 10 | 11 | 用两个栈实现队列。队列完成的操作为两个函数,一个可以在队尾插入数据,在队头删除数据。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 创建两个栈,分别为 stack1 和 stack2 。 18 | 19 | 入队: 20 | 21 | - 直接将元素进栈 stack 1。 22 | 23 | 出队: 24 | 25 | - 判断栈 2 是否为空,如果为空,就把 stack 1 中的所有元素 push 进栈 stack2 ; 26 | - 如果不为空,就直接出栈。 27 | 28 | 29 | 30 | #### 二、测试用例 31 | 32 | - 往非空的队列中添加、删除元素 —— 普通测试。 33 | - 往空的队列中添加、删除元素 —— 特殊测试。 34 | - 连续删除元素直至为空 —— 输入测试。 35 | 36 | 37 | 38 | #### 三、代码实现 39 | 40 | ```javascript 41 | function Stack(){ 42 | var item = []; 43 | this.push = function (node){ 44 | item.push(node); 45 | }; 46 | this.pop = function (){ 47 | return item.pop(); 48 | } 49 | this.isEmpty = function (){ 50 | return item.length === 0; 51 | } 52 | } 53 | 54 | var stack1 = new Stack(); 55 | var stack2 = new Stack(); 56 | 57 | // 入栈 58 | function push(node){ 59 | stack1.push(node); 60 | } 61 | 62 | // 出栈 63 | function pop(){ 64 | if(stack1.isEmpty() && stack2.isEmpty()){ 65 | throw new Error("Queue is empty"); 66 | } 67 | if(stack2.isEmpty()){ 68 | while(!stack1.isEmpty()){ 69 | stack2.push(stack1.pop()); 70 | } 71 | } 72 | return stack2.pop(); 73 | } 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /二叉搜索树的后续遍历序列.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/1 3 | Title: 二叉搜索树的后序遍历序列 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题三十三: 10 | 11 | 输入一个整数数组,判断该数组是不是某二叉搜索树的后续遍历。如果是返回 true,如果不是返回 false。假设输入的任意两个数字互不相同。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | **根据后续遍历的规律和二叉树具备的特点**,可以找到的规律就是(左、右、根)序列的最后一个数为根节点,又根据二叉树的特点,左子节点小于根节点,右子节点大于根节点,分离出左右子节点,根据上边的规律,**递归**剩下的序列。 18 | 19 | 20 | 21 | #### 二、测试用例 22 | 23 | - 完全二叉树、不完全二叉树 —— 普通测试 24 | - 只有左子节点的二叉树、只有右子节点的二叉树、只有一个节点的二叉树 —— 特殊测试 25 | - 空树 —— 输入测试 26 | 27 | 28 | 29 | #### 三、代码编写 30 | 31 | - 参数:数组 32 | - 判断数组是否为空 33 | - 取数组的最后一个元素作为对比的根节点 34 | - 根据根节点值的大小分割数组(分割数组的同时判断是否都满足小于根节点的要求) 35 | - 判断分割数组是否是空 36 | - 递归上方的步骤 37 | 38 | ```javascript 39 | const isPostorder = (arr)=>{ 40 | // 判断数组是否为 null 41 | if(arr.length == 0){ 42 | return true; 43 | } 44 | 45 | // 取数组最后一个数字为根节点 46 | let rootVal = arr[arr.length - 1]; 47 | 48 | // 搜索小于根节点的值,并记录该结点的下标(除根节点外) 49 | let i = 0; 50 | for(;i < arr.length - 1;i++){ 51 | if(arr[i] > rootVal){ 52 | break 53 | } 54 | } 55 | 56 | // 搜索大于根节点的值(除根节点外) 57 | let j = 0; 58 | for(;j < arr.length - 1; j++){ 59 | if(rootVal > arr[j]){ 60 | return false; 61 | } 62 | } 63 | 64 | // 递归判断左子节点的值(先判断左子节点是够有值),默认返回 true 65 | let left = true 66 | if(i > 0){ 67 | left = isPostorder(arr.slice(0, i)) 68 | } 69 | // 如果右子树不为空,判断右子树为二叉搜索树 70 | let right = true 71 | if(i < arr.length - 1){ 72 | right = isPostorder(arr.slice(i,arr.length - 1)) 73 | } 74 | return (left && right) 75 | } 76 | ``` 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ‘ -------------------------------------------------------------------------------- /二叉树中和为某一值的路径.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/2 3 | Title: 二叉树中和为某一值的路径 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题三十四: 10 | 11 | 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输出整数的所有路径。从树的根节点开始往下一直到叶子节点所经过的节点形成一条路径。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | **1、找规律:**需要遍历树的所有结点:我们会想到前、中、后遍历 18 | 19 | ​ : 需要存储遍历过的路径(节点值):我们想到用数组存储 20 | 21 | 22 | 23 | **2、算法思想:**前序遍历(根、左、右)的特点,从根到叶子节点,会从树自左向右依次遍历二叉树,所有可能的路径都会遍历到,所以使用前序遍历更佳。 24 | 25 | 每遍历一个结点就将其累加,然后判断累加的值是否等于目标值且子节点为叶子节点。如果是,则打印输出该路径;如果不是,则回退到上一父节点,此时数组中的数据结点进行删除,然后不断的遍历下一子节点,递归。 26 | 27 | **3、综上所述,**存储结点路径的时候,涉及到累加结点和删除节点,我们可以将其抽象成入栈和出栈。然后遍历二叉树的所有路径可以用到递归的过程,让出栈和入栈与递归的状态达成一致,这到题就不难了。 28 | 29 | 30 | 31 | #### 二、测试用例 32 | 33 | - 完全二叉树、非完全二叉树(有一条路径满足、有多条路径满足、都不满足)—— **普通测试**。 34 | - 只有左子节点的二叉树、只有右子节点的二叉树、只有一个结点的二叉树 —— **特殊测试**。 35 | - 空二叉树、输入负数 —— **输入测试**。 36 | 37 | 38 | 39 | #### 三、代码编写 40 | 41 | - 参数:二叉树、目标值 42 | - 判断二叉树是否为空和目标是是否是负数 43 | - 开始进行递归遍历二叉树进行查找满足条件的路径 44 | - 将当前递归的根节点进行累加 45 | - 同时该结点入栈 46 | - 47 | 48 | ```javascript 49 | const treeSum = (root, targetSum)=>{ 50 | // 判断输入的二叉树和整数 51 | if(root == null || targetSum < 0){ 52 | return false; 53 | } 54 | 55 | // 开始进行递归遍历二叉树进行查找满足条件的路径 56 | let result = []; // 存放最后满足条件的路径 57 | let pathStack = []; // 储存当前路径的栈 58 | let currentSum = 0; // 当前累加的结果值 59 | 60 | // 进行路径查找 61 | FindPath(root, targetSum, currentSum, pathStack, result); 62 | 63 | // 返回结果 64 | return result; 65 | } 66 | 67 | const FindPath = (root, targetSum, currentSum, pathStack, result)=>{ 68 | // 将当前跟根节点进行累加 69 | currentSum = currentSum + root.val; 70 | 71 | // 存储栈中 72 | pathStack.push(root.val); 73 | 74 | // 判断目标值是否相等且是否为叶子节点 75 | if(currentSum == targetSum && root.left == null && root.right == null){ 76 | // 打印路径 77 | result.push(pathStack.slice(0)) 78 | } 79 | 80 | // 如果左子节点不为空 81 | if(root.left !== null){ 82 | FindPath(root.left, targetSum, currentSum, pathStack, result); 83 | } 84 | 85 | // 如果当前结点还有右子树,继续遍历 86 | if(root.right !== null){ 87 | FindPath(root.right, targetSum, currentSum, pathStack, result); 88 | } 89 | 90 | // 该路径遍历到叶子节点,还没有满足条件,则退回到父节点,进行下一结点的累加判断 91 | pathStack.pop(); 92 | } 93 | ``` 94 | 95 | 96 | 97 | #### 四、小结 98 | 99 | - 当问题能够用递归去解决的时候,首先找到递归的点,比如二叉树的中的每个节点就是递归的点。 100 | 101 | - 当使用递归解决满足条件的问题时,直接每层递归进行判断,如果满足条件就处理,否则,递归自动跳过 if 判断。 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /二叉树搜索第 K 大结点.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9、3 3 | Title: 二叉树搜索第 K 大节点 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题五十四: 10 | 11 | 给定一棵二叉搜索树,请找出其中的第 K 大节点。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 要想找到第 K 大结点必要要知道排序,二叉树的前、中、后遍历中的中序遍历就是从小到大排序。然后遍历的同时计数找到第 K 大节点。 18 | 19 | 20 | 21 | #### 二、测试用例 22 | 23 | - 完全二叉树、非完全二叉树 —— 普通测试 24 | - 只有左子节点的二叉树、只有右子节点的二叉树、只有一个节点的二叉树 —— 特殊测试 25 | - K 的范围、空树 —— 输入测试 26 | 27 | 28 | 29 | #### 三、代码编写 30 | 31 | ```javascript 32 | // 求二叉树中第 K 大节点 33 | var kthTallest = function(root, k) { 34 | let res = [] 35 | // 遍历 36 | const inorder = (root) => { 37 | if (root) { 38 | inorder(root.left); 39 | res.push(root.val); 40 | inorder(root.right); 41 | } 42 | } 43 | // 调用 44 | inorder(root); 45 | return res[res.length - k] 46 | }; 47 | 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /二叉树的下一节点.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/8/27 3 | Title: 二叉树的下一结点 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题八:二叉树的下一节点 10 | 11 | 给定一个二叉树的节点,如何找出中序遍历的下一节点。有两个指向左右子树的指针,还有一个指向父节点的指针。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 求中序遍历的下一节点,就要分各种情况(明确中序遍历下一结点在二叉树中的位置有哪些),然后对某种情况详细分析。 18 | 19 | 下一结点可能存在的情况: 20 | 21 | - 有右子节点 22 | - 右子节点有无左子节点 23 | - 无 —— 右子节点就是当前结点下一节 24 | - 有 —— 递归寻找右子节点的左子节点就是下一节点 25 | - 无右子节点 26 | - 无父节点 —— 无下一结点 27 | - 有父节点 28 | - 当前结点作为父节点的左子节点 —— 下一结点为父节点 29 | - 当前结点作为父节点的右子节点 —— 向父节点递归寻找作为左子节点的结点就是下一节点 30 | 31 | 32 | 33 | #### 二、测试用例 34 | 35 | - 普通测试 —— 完全二叉树、非完全二叉树 36 | - 特殊测试 —— 只要左子节点的二叉树、只有右子节点的二叉树、只有一个结点 37 | - 输入测试 —— 空节点 38 | 39 | 40 | 41 | #### 三、代码实现 42 | 43 | ```javascript 44 | const getNextNode = (pNode)=>{ 45 | // 判断该结点是否为 null 46 | if(pNode == null){ 47 | return; 48 | } 49 | 50 | // 当前结点有右子树且左子树 51 | if(pNode.right !== null){ 52 | pNode = pNode.right; 53 | // 判断右子树是否有左子树 54 | while(pNode.left !== null){ 55 | pNode = pNode.left; 56 | } 57 | return pNode; 58 | }else{ 59 | // 判断当前结点是否存在父节点(如果为空,没有下一结点) 60 | while(pNode.next !== null){ 61 | if(pNode == pNode.next.left){ 62 | return pNode.next; 63 | }else{ 64 | pNode = pNode.next; 65 | } 66 | } 67 | // 没有下一结点 68 | return null; 69 | } 70 | } 71 | ``` 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /二叉树的深度.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/5 3 | Title: 二叉树的深度 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题五十五: 10 | 11 | 输入一棵二叉树的根节点,求该树的深度。从根节点到叶子节点依次经过的节点(包含根、叶子节点)形成树的一条路径,最长路径的长度树的深度。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 1、思路一:按层遍历,对按层遍历的算法进行改进,每遍历一次层进行加一。 18 | 19 | 2、思路二:寻找最长路径,借助遍历最长路径的设计思路记性改进。只需记录两个子树最深的结点为主。 20 | 21 | 22 | 23 | #### 二、测试用例 24 | 25 | - 完全二叉树、非完全二叉树 —— 普通测试 26 | - 只有左子节点、只有右子节点、只有一个结点二叉树 —— 特殊测试 27 | - 空树 —— 输入测试 28 | 29 | 30 | 31 | #### 三、代码编写 32 | 33 | ```javascript 34 | var maxDepth = function(root) { 35 | // 如果根节点为 null 36 | if(root === null) return 0; 37 | 38 | // 递归左子树 39 | let depthLeft = maxDepth(root.left); 40 | 41 | // 递归右子树 42 | let depthRight = maxDepth(root.right); 43 | 44 | // 将子问题合并求总问题 45 | return Math.max(depthLeft,depthRight) + 1; 46 | }; 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /二叉树的镜像.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/8/29 3 | Title: 二叉树的镜像 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题二十七: 10 | 11 | 请完成一个函数,如果一个二叉树,该函数输出它的镜像。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 根节点的左右子节点相互交换,继续递归遍历,将子节点的左右结点进行交换,知道遇到叶子节点。 18 | 19 | 20 | 21 | #### 二、测试用例 22 | 23 | - 普通二叉树 —— 普通测试 24 | - 只有左子节点、只有右子节点、只有一个结点 —— 特殊测试 25 | - 空树 —— 输入测试 26 | 27 | 28 | 29 | #### 三、代码实现 30 | 31 | ```javascript 32 | const insert = (root)=>{ 33 | // 判断根节点是否为 null 34 | if(root == null){ 35 | return; 36 | } 37 | 38 | // 进行结点交换 39 | Let tempNode = root.left; 40 | root.left = root.right; 41 | root.right = tempNode; 42 | 43 | // 递归遍历剩余的子节点 44 | insert(root.left); 45 | insert(root.right); 46 | 47 | // 返回根节点 48 | return root; 49 | } 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /二维数组中的查找.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/11 3 | Title: 二维数组中的查找 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题四: 10 | 11 | 在一个二维数组中,每一行都按照从左到右递增的顺序排序,从上到下递增的顺序排序。请输入一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。如下二维数组: 12 | 13 | ``` 14 | 1 2 8 9 15 | 2 4 9 12 16 | 4 7 10 13 17 | 6 8 11 15 18 | ``` 19 | 20 | 21 | 22 | #### 一、思路 23 | 24 | **技巧:**解决一个复杂的问题,好的方法就是从一个具体的问题入手,通过分析简单的例子,试图寻找规律。 25 | 26 | 仔细查看题目,寻找一组元素的规律,从边界开始寻找规律(左上、右上、左下、右下),可以一一测试发现,从右上开始比较数据发现规律。 27 | 28 | **规律:** 比如查找7,与 9 比较,小于 9 ,所以可以在 9 的左侧列继续查找,直到到 2 ,7 > 2 。然后开始往下寻找,4 < 7,继续递增寻找,直到找到 7 。 29 | 30 | 31 | 32 | #### 二、测试用例 33 | 34 | - 二维数组包含查找的数据和没有要查找的数据 —— 普通测试 35 | - 查找二维数组中最大的数据、最小的数据 —— 特殊测试 36 | - 空数组或 null、非整数 —— 输入测试 37 | 38 | 39 | 40 | #### 三、代码编写 41 | 42 | - 参数:数组、查找的值、行数、列数 43 | - 判断输入的值(行、列 > 0) 44 | - 右上角开始查找(查找的终止条件) 45 | 46 | ```javascript 47 | const findDoubleArrByValue = (arr, value, rows, columns)=>{ 48 | // 判断输入值 49 | if(arr == null || arr.length == 0){ 50 | return false; 51 | } 52 | var found = false; 53 | if(rows > 0 && columns > 0){ 54 | let row = 0; 55 | let column = columns - 1; 56 | 57 | while(row < rows && column >= 0){ 58 | if(arr[row][column] == value){ 59 | found = true; 60 | break; 61 | }else if(arr[row][column] > value){ 62 | column--; 63 | }else{ 64 | row++; 65 | } 66 | } 67 | } 68 | return found; 69 | } 70 | 71 | // 测试用例 72 | let arr = [[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]] 73 | console.log(findDoubleArrByValue(arr, 7, 4, 4)) 74 | ``` 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /从上打印二叉树.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/8/31 3 | Title: 从上到下打印二叉树 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题三十二: 10 | 11 | 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。(按层遍历二叉树) 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 从根节点开始按层遍历打印结点(自左往右),下一层的遍历是上一层的字节点,但是我们发现想要获取到上层结点的子节点时,上层的父节点已经遍历过去可,想要在获取到,必须存储父节点。然后下层遍历的时候,自左往右取出父节点,依次打印子节点。 18 | 19 | 上方的解题思路中父节点的存储和遍历让我们想到一个熟悉的数据结构,对了,“先进先出”的思想,那就是队列。在遍历上一层结点的时候,先打印结点值,然后判断是够存在左右子树,如果存在,将给结点入队,直到该层的结点全部遍历完成。然后队列出队,分别打印结点,循环此步骤。 20 | 21 | 22 | 23 | #### 二、测试用例 24 | 25 | - 完全二叉树、非完全二叉树 —— 普通测试 26 | - 只有左、右子节点的二叉树、只有一个节点的二叉树 —— 特殊测试 27 | - 空树 —— 输入测试 28 | 29 | 30 | 31 | #### 三、代码编写 32 | 33 | - 参数:树的根节点。 34 | - 判断是否为空。 35 | - 打印结点值,判断该结点是否存在子节点,如果存在就入队。 36 | - 出队,打印结点 37 | - 循环上述步骤 38 | 39 | ```javascript 40 | var levelOrder = function(root) { 41 | let result = []; // 存放遍历的结果 42 | // 判断根节点是否为 null 43 | if(root == null){ 44 | return []; 45 | } 46 | // 声明一个队列 47 | let queue = []; 48 | queue.push(root) 49 | 50 | // 出队,打印结结点、判断是否存在子节点 51 | while(queue.length !== 0){ 52 | let temp = []; // 存储每层的结点 53 | let len = queue.length; 54 | for(let j = 0;j < len;j++){ 55 | // 出队 56 | let tempNode = queue.shift(); 57 | // 存储结点值 58 | temp.push(tempNode.val) 59 | // 判断出队的根节点是否有子节点 60 | if(tempVal.left !== null){ 61 | queue.push(tempVal.left) 62 | } 63 | if(tempVal.right !== null){ 64 | queue.push(tempVal.left) 65 | } 66 | } 67 | //存储每层的遍历的结点值 68 | result.push(temp); 69 | } 70 | // 返回结果集 71 | return result; 72 | } 73 | ``` 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /剑指 offer 二叉树总结.md: -------------------------------------------------------------------------------- 1 | ## 剑指 offer 之二叉树面试题型总结 2 | 3 | ### 一、解题思路总结 4 | 5 | #### 1、根据树前(根左右)、中(左根右)、后(左右根)序遍历的规律来解决问题。 6 | 7 | > 通过二叉树的遍历来找到规律,从而找到解题思路。 8 | 9 | - 重建二叉树(题目详解) 10 | 11 | 根据前、中序遍历,找到二叉树的根节点和左右子树的规律,然后递归构建二叉树。 12 | 13 | - 二叉树的下一节点(题目详解) 14 | 15 | 根据中序遍历,找出包含任何节点的一下节点的所有可能情况,然后根据情况分别进行判断。 16 | 17 | - 二叉树的后续遍历序列(题目详解) 18 | 19 | 通过中序遍历找到打印二叉树结点的规律,可以判断此后续遍历是否为二叉树。 20 | 21 | - 二叉树和为某一值的路径(题目详解) 22 | 23 | 选择二叉树的遍历,对每个节点进行存储判断,然后根据二叉树叶子节点的特点,进行对问题的解决。 24 | 25 | - 二叉树的第 K 大结点(题目详解) 26 | 27 | 中序遍历的结果是从小到大,然后倒数找到第 K 大数据。 28 | 29 | - 序列化二叉树 30 | 31 | 遍历二叉树,遇到 null 转化为特殊符号。 32 | 33 | 34 | 35 | #### 2、根据树的结构寻找规律来解决问题 36 | 37 | > 通过二叉树的特点:左子节点小于父节点、右子节点大于父节点、树的节点可以进行递归等,以上特点又是更好的帮我们解决思路。 38 | 39 | - 树的子结构(题目详解) 40 | 41 | 根据子结构和主体树的特点,对其树的结构进行分析,可以找到解题的思路。 42 | 43 | - 镜像二叉树(题目详解) 44 | 45 | 观察镜像二叉树的左右子节点交换特点,可以找到解题思路。 46 | 47 | - 对称二叉树(题目详解) 48 | 49 | 观察对称二叉树有什么特点,在结构上和遍历上寻找特点和规律,可以找到解题思路。 50 | 51 | - 按层遍历二叉树(题目详解) 52 | 53 | 根据二叉树每层节点的结构关系(父子关系),可以进行每层遍历,通过上层找到下层的遍历结点。 54 | 55 | - 反序列化二叉树 56 | 57 | 根据遍历的规律和二叉树的规律,将遍历结果生成一棵二叉树。 58 | 59 | 60 | 61 | ### 二、测试用例 62 | 63 | 通过以上题目中,我将测试用例分为三大种,测试代码的时候,在这三大种进行想就可以了。 64 | 65 | - **普通测试** 66 | - **特殊测试** 67 | - **输入测试** 68 | 69 | 70 | 71 | #### 1、普通测试 72 | 73 | 普通测试从两个方面去想,第一个方面就是问题的本身,比如对称二叉树的判断,普通测试就是分别输入一个对称二叉树和非对称二叉树进行测试。第二个方面就是问题本身没有什么可以找到的测试,比如按层遍历二叉树,它的普通测试就是分别输入完全二叉树(普通二叉树也可以),非完全二叉树进行测试。 74 | 75 | 76 | 77 | #### 2、特殊测试 78 | 79 | 特殊测试强调的是树的特殊性,特殊的二叉树就那么几个,比如:只有左子节点的二叉树、只有右子节点的二叉树、只有一个节点的二叉树、没有结点的二叉树。 80 | 81 | 82 | 83 | #### 3、输入测试 84 | 85 | 输入测试,顾名思义,要对用户输入的参数进行判断,比如,你输入一棵树,要判断是否为空。再比如,求最大 K 结点,对 K 的取值范围进行判断。 86 | 87 | 88 | 89 | ### 三、代码编写 90 | 91 | 将二叉树的解题思路转化为代码除了熟练最基本的二叉树的增、删、改、查之外,最重要的就是二叉树的递归,因为二叉树的结构决定了用递归解决二叉树问题更加简便。但是递归的书写并不仅简单,因为它有递和归的过程,大脑并不能更好的去处理这些,可以去看之前总结递归的文章《[数据结构与算法之递归系列](https://github.com/luxiangqiang/Blog/blob/master/articel/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E7%B3%BB%E5%88%97/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E4%B9%8B%E9%80%92%E5%BD%92%E7%B3%BB%E5%88%97.md)》。 92 | 93 | 书写二叉树递归问题有一点特别重要,不要尝试的去想那个递归的过程,而是先去寻找到递归的终止条件,然后对每次递归的结果进行判断,然后让他递归去吧,再次强调千万别去思考过程。 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /剑指 offer 字符串的总结.md: -------------------------------------------------------------------------------- 1 | ## 剑指 offer 字符串的总结 2 | 3 | ### 一、解题思路总结 4 | 5 | #### 1、最低级思路 —— 暴力破解 6 | 7 | 通过对字符串题的总结,使用暴力破解法是最低级的方法,也是效率最低的方法,按照正常的思路遍历字符串的字符进行解决问题。 8 | 9 | - 基本所有题型都会有一个暴力破解法 10 | 11 | 12 | 13 | #### 2、哈希表法 14 | 15 | 很多字符串题目涉及到次数的,我们一听到次数,就立马想到用哈希表统计次数,然后进行下一步的处理,哈希表这种数据结构,一般键用来存储字符,值用来统计次数,而且操作数据的时间复杂度为 n(1)。所以借助哈希表能让你快速的解决问题的关键,而且效率也会得到大幅度提升。 16 | 17 | - [第一次只出现一次的字符串]([https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/第一个只出现一次的字符串.md)) 18 | 19 | 20 | 21 | #### 3、字符串转化数组更易解决 22 | 23 | 字符串的一些提醒,我们可以将字符串转化为数组操作,为什么呢?虽然字符串的自带的操作方法很多,但是有些不如数组方便,通常可以使用 `split` 方法将字符串转化为数组再操作,因为这样可以使用一些数组的方法,比如反转数组等。 24 | 25 | - [翻转字符串]([https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%8F%8D%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.md](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/反转字符串.md)) 26 | 27 | 28 | 29 | #### 4、递归 30 | 31 | 通常字符串题型用到递归,递归的使用条件,将大问题分解为小问题,而且大问题的解决方式和小问题的解决方法相同,这样使用递归更加的方便。 32 | 33 | - [字符串的排列]([https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%92%E5%88%97.md#%E4%BA%8C%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B](https://github.com/luxiangqiang/JianZhi-Offer_JavaScript/blob/master/字符串的排列.md#二测试用例)) 34 | 35 | 36 | 37 | ### 二、测试用例 38 | 39 | 通过以上题目中,我将测试用例分为三大种,测试代码的时候,在这三大种进行想就可以了。 40 | 41 | - **普通测试** 42 | - **特殊测试** 43 | - **输入测试** 44 | 45 | 46 | 47 | #### 1、普通测试 48 | 49 | > 根据题目的要求,进行简单测试,比如输入多个字符、带空格的字符等。 50 | 51 | 52 | 53 | #### 2、特殊测试 54 | 55 | > 进行特殊情况的处理,比如输入一个字符,所有字符都是唯一的字符、所有字符相同的字符串等特殊情况。 56 | 57 | 58 | 59 | #### 3、输入测试 60 | 61 | > 一般为输入空指针 null 和空字符串进行测试。 62 | 63 | -------------------------------------------------------------------------------- /剑指 offer 数组总结.md: -------------------------------------------------------------------------------- 1 | ## 剑指 offer 之数组面试题型总结 2 | 3 | ### 一、解题思路总结 4 | 5 | #### 1、数组中查找数据,潜意识要想到哈希表和二分查找 6 | 7 | > 一旦看到数组中查找数据或者题目暗示要你查找数据,我们第一要想到哈希表和二分查找能不能查找,一般暴力破解能解决,但是时间效率上不会太友好。 8 | 9 | **注意:**虽然哈希表时间效率很高,需要开辟内存空间。 10 | 11 | - 数组中重复的数据 12 | 13 | 通过哈希表来存储数据,变量数组,判断是否已经存在重复数据。 14 | 15 | - 旋转数组的最小数字 16 | 17 | 二分思想查找旋转数组中的最小数字,声明两个指针,分别指向数组的头部和尾部,取中间的元素,判断该元素是在前边的递增数组还是后边的递增数组。 18 | 19 | - 在排序数组中查找数字 20 | 21 | 直接查找到该数字在数组中第一次出现的位置和最后一次出现的位置,然后得出该数字出现的次数。 22 | 23 | 24 | 25 | #### 2、分治思想 26 | 27 | > 通过将大问题分解为小问题,将小问题解决后,然后合并小问题,大问题就得到解决。 28 | 29 | - 数组中出现次数超过一半的数字 30 | 31 | 借助快排 partition 函数的设计思想,选择一个区分点,然后将数组中其他数据按照该区分点进行左右分,左边的数小于区分点,右边的数大于区分点,区分之后,如果该区分点的坐标正好是该数据的中位数(n/2),那么该数字就是我们要找的数。否则的话,如果下标大于 n/2,那么中位数就在它的左边,否则在其右边查找。 32 | 33 | - 数组中的逆序对 34 | 35 | 通过归并排序的思想,将数据平均分成两部分,然后不断分,直到分为单个数据,然后分别求逆序对的数量,然后合并最后得到整个数组的逆序对。 36 | 37 | 38 | 39 | #### 3、观察数据本身的特点和规律 40 | 41 | > 考察应聘者的随机应变和查找规律和特点的能力,能够在数据中分析出规律且进行试验得出最终的应对方法。这种,难点在于要考虑到个别的边界条件。 42 | 43 | **小技巧:**解决一个复杂的问题,好的方法就是从一个具体的问题入手,通过分析简单的例子,试图寻找规律。 44 | 45 | - 二维数组中查找数据 46 | 47 | 寻找一组元素的规律,从边界开始寻找规律(左上、右上、左下、右下),可以一一测试发现,从右上开始比较数据发现规律。 48 | 49 | 50 | 51 | ### 二、测试用例 52 | 53 | 通过以上题目中,我将测试用例分为三大种,测试代码的时候,在这三大种进行想就可以了。 54 | 55 | - **普通测试** 56 | - **特殊测试** 57 | - **输入测试** 58 | 59 | 60 | 61 | #### 1、普通测试 62 | 63 | > 根据题目的涉及到的正常的情况进行测试,比如二维数组中的查找,二维数组包含查找的数据和没有要查找的数据为普通测试。 64 | 65 | 66 | 67 | #### 2、特殊测试 68 | 69 | > 对一些特殊的数据和边界条件进行测试,最大值、最小值、以及问题的特殊情况进行特殊测试。 70 | 71 | 72 | 73 | #### 3、输入测试 74 | 75 | > 对输入的数据进行测试,一般判断数据是否为空等。 76 | 77 | -------------------------------------------------------------------------------- /包含 min 函数的栈.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/10/28 3 | Title: 包含 main 函数的栈 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ### 面试题 30:包含 min 函数的栈 10 | 11 | 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数。在该栈中,调用 min、push及 pop 的时间复杂度为 O(1)。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 创建两个栈,一个是数据栈,一个是辅助栈。 18 | 19 | - **入栈:当元素压入数据栈的时候。** 20 | - 应先判断数据栈是否为空,为空时,将新元素压入数据栈和辅助栈,因为此时元素为最小元素。 21 | - 如果数据栈不为空,就拿出栈中最小的元素进行比较(辅助栈中顶部元素),小于则该元素同时入两个栈,否则只进入数据栈。 22 | 23 | 24 | 25 | - **出栈:当元素出栈的时候。** 26 | 27 | - 如果取出的元素正好是栈中的最小元素,则数据栈和辅助栈同时出栈。 28 | - 否则,只出栈数据栈。 29 | 30 | 31 | 32 | 33 | 34 | - **栈中最小元素:** 35 | - 直接在辅助栈中取出最小元素。 36 | 37 | 38 | 39 | #### 二、测试用例 40 | 41 | - 新压入栈的数据比之前大、新压入栈的数据比之前小 —— 普通测试。 42 | 43 | - 弹出栈的数字是最小元素、弹出栈的不是最小元素 —— 特殊测试。 44 | - 输入的元素不能为 null —— 输入测试。 45 | 46 | 47 | 48 | #### 三、代码实现 49 | 50 | ```javascript 51 | // 数据栈 52 | let temp = []; 53 | // 辅助栈 54 | let temp1 = []; 55 | 56 | // 出栈 57 | function top1(){ 58 | if(temp.length > 0){ 59 | return temp[temp.length - 1]; 60 | }else{ 61 | return null; 62 | } 63 | } 64 | 65 | // 入栈 66 | function push(value){ 67 | let popValue = top1(); 68 | // 判断是否为第一个数据栈是否有已经存在元素 69 | // 如果存在,则与栈最小元素进行比较 70 | // 无数据第一个元素就是最小元素、与辅助栈最小元素比较最小 71 | if(!popValue || value < temp1[temp1.length - 1]){ 72 | temp.push(value) 73 | temp1.push(value) 74 | }else if(value >= temp1[temp1.length - 1]){ 75 | // 如果入栈元素比辅助栈元素大,则只进入数据栈 76 | temp.push(value) 77 | temp1.push(temp1[temp1.length - 1]) 78 | } 79 | } 80 | 81 | // 出栈 82 | function pop(){ 83 | let popValue = top1(); 84 | // 如果出栈的是最小元素,则两个栈的长度都减一 85 | if(popValue === temp1[temp1.length - 1]){ 86 | result = temp[temp.length - 1]; 87 | temp.length--; 88 | temp1.length--; 89 | }else{ 90 | // 只减数据栈的大小 91 | temp.length--; 92 | } 93 | return popValue; 94 | } 95 | 96 | // 取出最小值 97 | function min(){ 98 | return temp1[temp.length - 1]; 99 | } 100 | 101 | // 测试用例 102 | push(5) 103 | push(1) 104 | push(6) 105 | push(8) 106 | pop() 107 | pop() 108 | console.log(min()); 109 | pop(); 110 | console.log(min()); 111 | console.log(temp) 112 | console.log(temp1) 113 | ``` 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /反转字符串.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/10/18 3 | Title: 翻转字符串 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ### 面试题58:(重点) 10 | 11 | 输入一个英文句子,翻转句子中的单词顺序,但单词内字符的顺序不变,标点符合和普通字母一样处理。例如:`I am student`,则输出 `.student an I`。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 第一步,反转所有的字符串。此时变为 `tneudts. a am I` 。此时每个单词的字符都被翻转了,第二步,再翻转每个单词中字符中的顺序。 18 | 19 | 20 | 21 | #### 二、测试用例 22 | 23 | - 句子中有多个单词 —— 普通测试 24 | - 句子中只有一个单词 —— 特殊测试 25 | - 输入空指针和空字符串 —— 输入测试 26 | 27 | 28 | 29 | #### 三、代码实现 30 | 31 | ```javascript 32 | const Reverse = (str)=>{ 33 | // 判断字符串是否为 null 或者 空字符串 34 | if(str == null || str == ""){ 35 | return ""; 36 | } 37 | 38 | // 第一次反转,将字符串转化为数组进行反转 39 | let arr = str.split('').reverse(); 40 | // 第二次反转,将每个单词的字符反转 41 | let i = 0; 42 | let start = 0; 43 | let end = 0; 44 | let result = []; 45 | while(i < arr.length){ 46 | if(arr[i] == " "){ 47 | result = result.concat(arr.slice(start,i).reverse()) 48 | start = i + 1; 49 | end = i + 1; 50 | result.push(" ") 51 | } 52 | i++; 53 | } 54 | result = result.concat(arr.slice(end)) 55 | return result; 56 | } 57 | 58 | // 测试代码 59 | let str = 'I am Student.'; 60 | console.log(Reverse(str)); 61 | ``` 62 | 63 | -------------------------------------------------------------------------------- /在排序数组中查找数字.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/23 3 | Title: 在排序数组中查找数字 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题五十三: 10 | 11 | 统计一个数字在排序数组中出现的次数。例如,输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于 3 在这个数组中出现了 4 次,因此输入 4 。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | **1、查找 —— > 潜意识:哈希表、二分查找** 18 | 19 | - 首先最先想到的就是从头遍历数组,查找该数字出现的次数,它的时间复杂度为 O(n),如果要求更快效率的查找,该方法不适合。 20 | 21 | - 二分查找,直接查找到该数字在数组中第一次出现的位置和最后一次出现的位置,然后得出该数字出现的次数。 22 | 23 | 24 | 25 | #### 二、测试用例 26 | 27 | - 数组中包含要查找的数字、数组中没有要查找的数字、该数字在数组中出现一次/多次 —— 普通测试。 28 | - 查找数组中的最大值、最小值、数组汇总只有一个数字 —— 特殊测试。 29 | - 数组为空 —— 输入测试。 30 | 31 | 32 | 33 | #### 三、代码实现 34 | 35 | ```javascript 36 | // 查找数字在数组中第一个数字 37 | const twoFindFirst = (arr, value)=>{ 38 | // 判断数组是否为空 39 | if(arr == null || arr.length == 0){ 40 | return -1; 41 | } 42 | // 定义两个指针 43 | let low = 0; 44 | let high = arr.length - 1; 45 | 46 | // 开始查找 47 | while(low <= high){ 48 | // 取中 49 | const middle = Math.floor((low + high) / 2); 50 | 51 | // 判断是否等于目标值 52 | if(arr[middle] === value){ 53 | // 判断是第几个 54 | if(middle == 0 || value !== arr[middle - 1]){ 55 | return middle; 56 | }else{ 57 | high = middle - 1; 58 | } 59 | }else if(arr[middle] > value){ 60 | high = middle - 1; 61 | }else{ 62 | low = middle + 1; 63 | } 64 | } 65 | return -1; 66 | } 67 | 68 | 69 | // 查找数组中最后一次出现的数字 70 | const twoFindLast = (arr,target) => { 71 | // 判断数组是否为空 72 | if(arr == null || arr.length == 0){ 73 | return -1; 74 | } 75 | 76 | let low = 0 77 | let high = arr.length - 1; 78 | while(low <= high){ 79 | const mid = Math.floor((low + high) / 2); 80 | if(arr[mid] === target){ 81 | if(mid == arr.length-1 || target != arr[mid + 1]){ 82 | return mid; 83 | }else{ 84 | low = mid + 1; 85 | } 86 | }else if(target < arr[mid]){ 87 | high = mid - 1; 88 | }else{ 89 | low = mid + 1; 90 | } 91 | } 92 | return -1; 93 | } 94 | ``` 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /字符串的排列.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/10/11 3 | Title: 字符串的排列 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ### 面试题38: 10 | 11 | 输入一个字符串,打印出该字符串中字符的所有排列。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 将整个字符串看做成两部分,第一个字符为一部分,剩余的字符为另一部分;第二步就是第一个字符不变,不断的用第二部分的字符替换第一部分的字符。 18 | 19 | 替换完成之后。把剩余第二部分 的字符按照同样的思想进行,其实这就是一个递归的过程。 20 | 21 | 22 | 23 | #### 二、测试用例 24 | 25 | - 输入多个字符 —— 普通测试。 26 | - 输入一个字符 —— 特殊测试。 27 | - 输入空字符串和空指针 —— 输入测试。 28 | 29 | 30 | 31 | #### 三、代码实现 32 | 33 | ```javascript 34 | const Permutation = (str)=>{ 35 | // 将所有排序的可能存放到数组中 36 | var result = []; 37 | 38 | // 判断字符串的长度 39 | if(str.length <= 0){ 40 | return []; 41 | } 42 | 43 | var sortTemp = ""; // 临时排序的字符串 44 | var arr = str.split(""); // 将字符串分割成数组 45 | 46 | // 递归排序 47 | result = sortString(arr, sortTemp, []); 48 | 49 | // 返回结果 50 | return result; 51 | } 52 | // 对数组的字符进行排序 53 | const sortString = (arr, sortTemp, res)=>{ 54 | // 判断数组是否为空 55 | if (arr.length == 0) { 56 | res.push(sortTemp); 57 | } else { 58 | // 借助对象属性时不能重复的字符串的特性进行去重 59 | var isRepeat = {}; 60 | // 组排字符 61 | for (var i = 0; i < arr.length; i++) { 62 | // 如果当前的字符没有存在对象中(不是重复的) —— 已经设置过字符 63 | if(!isRepeat[arr[i]]){ 64 | var temp = arr.splice(i, 1)[0]; // 取出第i个字符 65 | sortTemp += temp; // 将第一个字符设置为前缀 66 | 67 | arr.splice(i, 0, temp); // splice 方法会改变原来的数组,所以要补全 68 | sortTemp = sortTemp.slice(0, sortTemp.length - 1); // 清空 sortTemp 69 | isRepeat[temp] = true; // 已经设置过此数据 70 | } 71 | } 72 | } 73 | return res; 74 | } 75 | 76 | let str = 'abc'; 77 | console.log(Permutation(str)); 78 | ``` 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /对称的二叉树.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/8/30 3 | Title: 对称的二叉树 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题二十八: 10 | 11 | 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 1、首先,观察一个对称的二叉树有什么特点? 18 | 19 | - 结构上:在结构上实对称的,某一节点的左子节点和某一节点的右子节点对称。 20 | - 规律上:我们如果进行前序遍历(根、左、右),然后对前序遍历进行改进(根、右、左),如果是对称的二叉树,他们的遍历结果是相同的。 21 | 22 | 2、考虑其他情况 23 | 24 | - 结点数量不对称 25 | - 结点值不对称 26 | 27 | 28 | 29 | #### 二、测试用例 30 | 31 | - 对称二叉树、不对称二叉树(结点数量不对称、结点结构不对称) —— 普通测试 32 | 33 | - 所有结点值都相同的二叉树 —— 特殊测试 34 | - 空二叉树 —— 输入测试 35 | 36 | 37 | 38 | #### 三、代码编写 39 | 40 | ```javascript 41 | var isSymmetric = (root)=>{ 42 | // 判断二叉树是否为 null —— 输入测试, if(root == null){ 43 | return true; 44 | } 45 | 46 | // 判断输入的二叉树,从根节点开始判断是否是对称二叉树 47 | var Symmetric = (lNode, rNode)=>{ 48 | // 判断左右结点是否都为 null 49 | if(lNode == null && rNode == null){ 50 | return true; 51 | } 52 | // 判断其中一个为 null 另一个不是 null 53 | if(lNode == null && rNode !== null){ 54 | return false; 55 | } 56 | if(lNode !== null && rNode == null){ 57 | return false; 58 | } 59 | // 判断两个结点的值是否相同 60 | if(lNode.val !== rNode.val){ 61 | return false; 62 | } 63 | // 如果相同,继续递归判断其他的结点 64 | return Symmetric(lNode.left,rNode.right) && Symmetric(lNode.right,rNode.left) 65 | } 66 | 67 | Symmetric(root.left,root.right) 68 | } 69 | ``` 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /序列化二叉树和反序列化二叉树.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/4 3 | Title: 序列化和反序列化二叉树 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题三十七: 10 | 11 | 请实现两个函数,分别用来序列化二叉树和反序列化二叉树。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 1、序列化:遍历二叉树,遇到叶子节点,将其转化为 $ 表示。 18 | 19 | 2、反序列化:根据前序遍历的特点(根、左、右),进行二叉树的还原。 20 | 21 | 22 | 23 | #### 二、测试用例 24 | 25 | - 完全二叉树、非完全二叉树 —— 普通测试 26 | - 只有左子节点、只有右子节点、只有一个节点 —— 特殊测试 27 | - 空数组、空树 —— 输入测试 28 | 29 | 30 | 31 | #### 三、代码编写 32 | 33 | - 序列化: 34 | 35 | ```javascript 36 | let result = []; 37 | var serialize = function(root) { 38 | // 判空 39 | if(root == null){ 40 | result.push('$'); 41 | return; 42 | } 43 | // 前序遍历 44 | result.push(root.val) 45 | serialize(root.left) 46 | serialize(root.right) 47 | // 打印 48 | console.log(result) 49 | }; 50 | 51 | serialize(symmetricalTree); 52 | ``` 53 | 54 | - 反序列化: 55 | 56 | ```javascript 57 | // 反序列化二叉树 58 | var deserialize = function(arr) { 59 | // 判空 60 | if(arr.length == 0){ 61 | return null; 62 | } 63 | 64 | // 出栈队判断 65 | let node = null; 66 | const val = arr.shift(); 67 | if(val !== '$'){ 68 | node = { 69 | val: val 70 | }; 71 | node.left = deserialize(arr); 72 | node.right = deserialize(arr); 73 | } 74 | return node; 75 | }; 76 | let str = '8,6,5,$,$,7,$,$,6,7,$,$,5,$,$'; 77 | console.log(deserialize(str.split(','))); 78 | ``` 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /数组中出现次数超过一半的数字.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/15 3 | Title: 数组中出现次数超过一半的数字 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题三十九: 10 | 11 | 数组中有一个数字出现的次数超过数组长度的一半,请找出下这个数字。例如,输入一个长度为 9 的数组{1,2,3,2,2,2,5,4,2}。由于数字 2 在数组中出现了 5 次,超过数组长度的一半,所以输出 2。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | **1、思路一:**一说到查找,应该想到哈希表、双指针,该题可以使用哈希表来解决,遍历整个数组,如果哈希表中存在该数据,就计数加1,否则将该数据存入哈希表中,但是有一个缺点就是空间复杂度为 O(n)。 18 | 19 | **2、思路二:**借助快排 partition 函数的设计思想,选择一个区分点,然后将数组中其他数据按照该区分点进行左右分,左边的数小于区分点,右边的数大于区分点,区分之后,如果该区分点的坐标正好是该数据的中位数(n/2),那么该数字就是我们要找的数。否则的话,如果下标大于 n/2,那么中位数就在它的左边,否则在其右边查找。 20 | 21 | > PS:因为如果一组数据有超过数组一半的数字的话,从小到大排列,其中中位数就是我们要查找的数字。 22 | 23 | **3、思路三:** 可以说是思路一的一种优化,因为根数组的特点,超过一半的数据总是比其他数据的总数要多,所以用一个标识来计数,如果下一个数字是同一个,计数加1,否则计数-1。最后如果 count 不为 0 的时候,说明有超过一半的数字。 24 | 25 | (注意:这里需要做一个检查,因为如果是奇数数量的话,没有超过一半的数字 count 也不为 0 的) 26 | 27 | 28 | 29 | #### 二、测试用例 30 | 31 | - 重复数字超过一半的数组、没有重复数据的数组 —— 普通测试 32 | - 空数组 —— 输入测试 33 | 34 | 35 | 36 | #### 三、代码编写 37 | 38 | ```javascript 39 | const MoreThanHalfNum = (arr)=>{ 40 | // 判断数组是否为null 41 | if(arr == null || arr.length == 0){ 42 | return false; 43 | } 44 | 45 | let count = 1; 46 | let number = arr[0]; 47 | let i = 1; 48 | while(i < arr.length){ 49 | // 判断遍历的数据是否和当前的数据相同 50 | if(arr[i] == number){ 51 | count++ 52 | }else{ 53 | // 判断当前 count 是否等于 0 54 | if(count == 0){ 55 | number = arr[i]; 56 | count = 1; 57 | }else{ 58 | count--; 59 | } 60 | } 61 | i++; 62 | } 63 | 64 | // 判断当前数组是否有超过一半数组的数据 65 | if(count > 0){ 66 | return number; 67 | }else{ 68 | return -1; 69 | } 70 | 71 | // 需要进行一个错误检查 —— 如果没有超出数组的一班,就报错 72 | // check(); 73 | } 74 | 75 | // 测试用例 76 | let arr = [1,2,3,4,5,6,7,8,9] 77 | console.log(MoreThanHalfNum(arr)) 78 | ``` 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /数组中的逆序对.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/18 3 | Title: 数组中的逆序对 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题五十一: 10 | 11 | 输入一个数组,统计这个数组中出现的逆序对的总数。例如{7,5,6,4},共有五个逆序对。 12 | 13 | 14 | 15 | #### 一、思路(分治思想) 16 | 17 | 1、最坏的做法就是依次遍历数组中的数字,然后依次计算逆序对的个数,但是时间复杂度非常高,O(n²)。 18 | 19 | 2、归并排序的思想:先将数组分割成子数组,然后统计出每个数组中的逆序对的数目,然后统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,需要对数组进行排序。 20 | 21 | 22 | 23 | #### 二、测试用例 24 | 25 | - 普通数组 —— 普通测试 26 | - 数组从小到大排序、数组从小到大排序 —— 特殊测试 27 | - 空数组 —— 输入测试 28 | 29 | 30 | 31 | #### 三、代码实现 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /数组中重复的数字.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/10 3 | Title: 数组中重复的数字 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题三: 10 | 11 | 找出数组中重复的数字。 12 | 13 | 在一个长度为 n 的数组里的所有数字都在 0 - n-1 的范围内。数组中的某些数字是重复的,但不知道有几个数字重复,也不知道重复了几次,请找出数组中任意一个重复的数字。 14 | 15 | 16 | 17 | #### 一、思路 18 | 19 | **1、思路一:**先进行数据的排序,然后就可以找到重复的数字(但是时间复杂度非常的高) 20 | 21 | **2、思路二:**散列表(哈希表),一说到查找,首先想到效率最高的就是哈希表。遍历所有的数据,在散列表中查找,如果存在数据,则为重复数字,否则将该数据插入到该哈希表中。(空间复杂度为 O(n)) 22 | 23 | **3、思路三:**题目中说到的是所有数字在 0 - n-1 范围内,所以遍历数组,数组中的每个元素都要和下标对比是否相等,如果是,继续扫描,否则就要和该元素为下标值查找到相同的元素进行比较,如果相同,则为重复数字,否则两个数据进行交换。 24 | 25 | 26 | 27 | #### 3、测试用例 28 | 29 | - 多个重复元素的数组 —— 普通测试 30 | - 一个重复元素的数组、没有重复元素的数组 —— 特殊测试 31 | - 空数组 —— 输入测试 32 | 33 | 34 | 35 | #### 4、代码编写 36 | 37 | - 测试数组:{2, 3, 1, 0, 2, 5, 3} 38 | 39 | ```javascript 40 | const result = []; 41 | const RepeatingNumber = (arr)=>{ 42 | // 判断数组是否为空 43 | if(arr.length <=0 || arr == null){ 44 | return -1; 45 | } 46 | // 定义散列表 47 | const map = new Map(); 48 | 49 | arr.forEach(function(item, index, arr){ 50 | if(map.has(item)){ 51 | result.push(item); 52 | }else{ 53 | map.set(item,item) 54 | } 55 | }) 56 | } 57 | 58 | let arr = [2, 3, 1, 0, 2, 5, 3] 59 | RepeatingNumber(arr) 60 | console.log(result) 61 | ``` 62 | 63 | -------------------------------------------------------------------------------- /旋转数组中的最小数字.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/13 3 | Title: 旋转数组中的最小数字 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题十一: 10 | 11 | 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如:数组 {3,4,5,1,2} 为 {1,2,3,4,5}的一个旋转,该数组的最小值为 1. 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 一提到查找,下意识的想到: 18 | 19 | - **哈希表思想** 20 | - **二分查找思想** √ 21 | 22 | 1、特性:旋转数组 23 | 24 | 2、规律:二分思想查找旋转数组中的最小数字,声明两个指针,分别指向数组的头部和尾部,取中间的元素,判断该元素是在前边的递增数组还是后边的递增数组。如果是在前边的递增数组,那么最小数字在后边的递增数组,所以移动前指针,然后再后边的进行二分查找,否则相反。最后两个指针必定指向一个指向递增数组的数字,另一个指针指向后边自增的数字,所以第二个指针必定指向最小数字。 25 | 26 | **特殊情况:** 27 | 28 | - 数组中有重复的数字,比如:{1,0,1,1,1},{1,1,1,0,1}。 29 | - 旋转数组向后旋转 0 个元素(还是本身,此时数组中的第一个元素小于数组的最后一个元素)。 30 | 31 | 32 | 33 | #### 二、测试用例 34 | 35 | - 数组升序数字 —— 普通测试 36 | - 有重复的数字的数组、只有一个数字的数组 —— 特殊测试 37 | - 空数组 —— 输入测试 38 | 39 | 40 | 41 | #### 三、代码实现 42 | 43 | ```javascript 44 | const Min = (arr)=>{ 45 | // 判断输入的数组 46 | if(arr == null || arr.length == 0){ 47 | return null; 48 | } 49 | 50 | // 定义头尾指针 51 | let head = 0; 52 | let tail = arr.length - 1; 53 | let middleIndex = head; 54 | 55 | // 终止条件 56 | while(arr[head] >= arr[tail]){ 57 | 58 | if(tail - head == 1){ 59 | middleIndex = tail; 60 | break; 61 | } 62 | 63 | // 二分查找取中间元素 64 | middleIndex = (head + tail) / 2 65 | 66 | // 判断是否为最小值 67 | // 如果头指针、尾指针、中间元素的值为相同,需要顺序遍历寻找最小数字 68 | if(arr[middleIndex] == arr[head] && arr[head] == arr[tail]){ 69 | return 70 | } 71 | 72 | // 正常情况 73 | if(arr[middleIndex] >= arr[head]){ 74 | head = middleIndex 75 | }else if(arr[middleIndex] <= arr[tail]){ 76 | tail = middleIndex 77 | } 78 | } 79 | // 否则就是最小值 80 | return arr[middleIndex]; 81 | } 82 | 83 | // 顺序查找最小元素 84 | const MinInOrder = (arr, head, tail)=>{ 85 | let result = arr[head]; 86 | for(let i = head + 1;i <= tail; ++i){ 87 | if(result > arr[i]){ 88 | result = arr[i]; 89 | } 90 | } 91 | return result; 92 | } 93 | ``` 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /替换空格.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/10/8 3 | Title: 替换空格 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ### 面试题五:替换空格 10 | 11 | 请实现一个函数,把字符串中的每个空格替换成 “20%”。例如,输入“We are happy.”,则输出 “We%20are%20happy.”。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | **1、思路一:**逐次遍历字符串的每个字符,遇到空格我们就替换成 %20,每替换一个空格,后边的字符串都要进行移动,在最坏的情况下,时间复杂度为O(n²)。但是要考虑一个问题就是,此时字符串的长度会增加,要注意内存的消耗问题。 18 | 19 | > PS: 注意要判断是在原来的字符串上更改还是需要创建新的字符串,都要注意内存的消耗。 20 | 21 | **2、思路二:双指针 ** 22 | 23 | 首先,先遍历整个字符串,找到空格数量,然后可以根据计算出替换后的字符串的总长度。声明两个指针,一个指向原始字符串的末尾,另一个指向替换之后的字符串的末尾。逐渐的复制,遇到空格就将 20% 替换,当两个指针重合的时候,说明字符串中空格已经替换完了。 24 | 25 | 26 | 27 | #### 二、测试用例 28 | 29 | - 字符串中包含空格、包含连续的空格、空格位于最前边、空格位于最后边 —— 普通测试 30 | - 字符串是一个空字符串 —— 特殊测试 31 | - 输入的字符串为 null —— 输入测试 32 | 33 | 34 | 35 | #### 三、代码实现 36 | 37 | ```javascript 38 | // 逐次判断 39 | function replaceSpace(str) 40 | { 41 | let output=''; 42 | for(let i = 0,len = str.length;i < len;i++){ 43 | if(str[i] == ' '){ 44 | output += '%20'; 45 | }else{ 46 | output += str[i]; 47 | } 48 | } 49 | return output; 50 | } 51 | ``` 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /最长不含重复字符的子字符串.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/10/13 3 | Title: 最长不含重复字符的子字符串 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ### 面试题48: 10 | 11 | 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。例如:`arabcacfr` 。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | **1、思路一:**普通思路的话,直接找出所有的字符串情况,然后逐个判断是否包含重复的字符,这样时间复杂度比较高,是 `O(n3)`。 18 | 19 | **2、思路二:**动态规划。 20 | 21 | - 定义函数 `f(i) = f(i - 1) + 1`,`f(i)`代表第` i` 个字符为结尾的不包含重复字符的子字符串的最长长度。 22 | - 从左到右扫描每个字符,第 `i` 字符结尾的最长字符`f(i)`,`f(i-1) ` 我们已经知道。比如上方的例子,第 0 个字符为 `f(0) = 1`, `f(1) = f(0) + 1`等于2,也就是当前最长不重复字符为 2,也就是 “`ar`”。 23 | - 如果之前出现过,分情况: 24 | - **第一种情况:**在当前连续的字符中有重复的。比如当 ` i = 2 ` 时,与之前重复字符的距离为 `d = 2 f(i-1)`,此时去掉前边重复的字符。 25 | - **第二种情况:**当重复的字符不在当前连续的字符中。`arabcacfr` 中的 `acfr` 最后一个重复的 `i = 1` 情况,此时 d = 7,这种情况就要把等于 8 的字符拼接到后边 `f(8) = f(7) + 1`。 26 | 27 | 28 | 29 | #### 二、测试用例 30 | 31 | - 多个字符的字符串 —— 普通测试。 32 | - 只有一个字符的字符串、所有字符都是唯一的字符串、所有字符都相同 —— 特殊测试。 33 | - 空字符串 —— 特殊输入 34 | 35 | 36 | 37 | #### 三、代码实现 38 | 39 | ``` 40 | 41 | ``` 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /树的子结构.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/8/28 3 | Title: 树的子结构 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题二十六:树的子结构 10 | 11 | 输入两棵二叉树 A 和 B,判断 B 是不是 A 的子结构。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 通过判断两棵树的根节点否相同,如果相同,则递归判断树剩余的结点是否相同。如果不相同,则递归树的左右子节点进行对比找到相同的根节点。 18 | 19 | 20 | 21 | #### 二、测试用例 22 | 23 | - 是子结构、不是子结构 —— 普通测试。 24 | - 只有左子节点、只有右子节点、只有一个结点 —— 特殊测试。 25 | - 空树 —— 输入测试。 26 | 27 | 28 | 29 | #### 三、代码实现 30 | 31 | ```javascript 32 | const TreeConstrutor = (nodeA, nodeB)=>{ 33 | const result = false; 34 | // 判断输入是否为 null 35 | // nodeA 为 null 不会有子结构 36 | if(nodeA == null){ 37 | return false; 38 | } 39 | // 如果 nodeB 为 null,代表所有子结构比较完成 40 | if(nodeB == null){ 41 | return true; 42 | } 43 | 44 | // 如果根节点相同,则进行子结构全部的验证,返回验证的结果 45 | if(nodeA.data === nodeB.data){ 46 | result = match(nodeA, nodeB) 47 | } 48 | 49 | // 如果根节点不相同,继续递归遍历查找相同的根节点 50 | return TreeConstrutor(nodeA.left, nodeB) || TreeConstrutor(nodeA.right, nodeB) 51 | } 52 | 53 | // 匹配根节点相同的子结构 54 | const match = (nodeA, nodeB)=>{ 55 | if(nodeA == null){ 56 | return false; 57 | } 58 | if(nodeB == null){ 59 | return true; 60 | } 61 | // 判断匹配的当前结点是否相同 62 | if(nodeA.data == nodeB.data){ 63 | // 递归匹配其他子节点 64 | return match(nodeA.left, nodeB.left) && match(nodeA.right, nodeB.right); 65 | } 66 | 67 | // 如果不相同 68 | return false; 69 | } 70 | ``` 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /第一个只出现一次的字符串.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/10/21 3 | Title: 第一个只出现一次的字符串 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ### 面试题50: 10 | 11 | 字符串中第一个只出现一次的字符。例如:abaccdeff,则输出 ‘b’。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | ##### 1、思路二: 暴力破解法 18 | 19 | 从头到尾依次遍历数据,每遍历一个数据,就判断该数据是不是出现的第一次,遍历的时间复杂度为O(n),共有 n 个元素,需要遍历 n 次,所以时间复杂度为 O(n²)。 20 | 21 | 22 | 23 | ##### 2、思路二:哈希表 24 | 25 | 利用哈希表的特性,扫描一遍字符串,用哈希表存储每个字符出现的次数,时间复杂度为O(n)。然后再遍历哈希表,找到第一次只出现一次的字符。 26 | 27 | 28 | 29 | #### 二、测试用例 30 | 31 | - 字符串中存在只出现一次的字符 —— 普通测试。 32 | - 字符串中没有只出现一次的字符、字符串中所有字符只出现一次—— 特殊测试。 33 | - 字符串为 null、空字符串 —— 输入测试。 34 | 35 | 36 | 37 | #### 四、代码实现 38 | 39 | ```javascript 40 | const FindNotRepeating = (str)=>{ 41 | // 判断字符串是否为 null 或者为空字符串 42 | if(str == null || str.trim() == ''){ 43 | return -1; 44 | } 45 | 46 | var hash = new Map(); 47 | var arr = str.split(''); 48 | // 遍历字符串统计数量 49 | for(let index in arr){ 50 | if(hash.has(arr[index])){ 51 | hash.set(arr[index],hash.get(arr[index]) + 1); 52 | }else{ 53 | hash.set(arr[index],1) 54 | } 55 | } 56 | // 寻找第一次只出现一次的字符 57 | for(let [key,value] of hash){ 58 | if(value === 1){ 59 | return key; 60 | } 61 | } 62 | return -1; 63 | } 64 | // 测试用例 65 | let str = 'abaccdeff'; 66 | console.log(FindNotRepeating(str)) 67 | ``` 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /表示数值的字符串.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/10/9 3 | Title: 表示数值的字符串 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ### 面试题20: 10 | 11 | 请实现一个函数用来判断字符串是否表示的是数值(包括整数和小数)。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | ##### 1、情况分析:A[.[B]]/[e|EC] 或者 .B[e|EC] 18 | 19 | > ① 在小数里可能没有数值的整数部分,比如:0.123 和 .123 是相同的。 20 | > 21 | > ② 如果一个数没有整数部分,那么它的小数部分不能为空。 22 | 23 | - A 为整数部分 24 | - B 为紧跟的小数部分 25 | - C 为紧跟着 e 或者 E 为数值的指数部分 26 | 27 | - A 和 C 都是可以有正负号的 0 ~ 9 的数位串。 28 | - B 是没有正负号的 0 ~ 9 的数位串 29 | 30 | 31 | 32 | ##### 2、思路一: 33 | 34 | 对字符串进行扫描,扫描 0 ~ 9 个数位(有可能有正负号),这是 A 部分。遇到小数点,开始扫描小数部分(即B部分)。如果遇到 E / e ,开始扫描指数部分。 35 | 36 | 37 | 38 | #### 二、测试用例 39 | 40 | - 正数或者负数;—— 普通测试 41 | 42 | - 包含或者整数部分数值;包含不包含小数部分的数值;包含不包含指数部分的数值;各种不能表达有效数值的字符串。—— 特殊测试 43 | 44 | - 输入 null 空指针、空字符串 —— 输入测试 45 | 46 | 47 | 48 | #### 三、代码实现 -------------------------------------------------------------------------------- /调整数组的顺序使其奇数位于偶数的前面.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/14 3 | Title: 调整数组的顺序使其奇数位于偶数的前面 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题二十一: 10 | 11 | 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使其所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 12 | 13 | 2134 14 | 15 | #### 一、思路 16 | 17 | 1、普通思路:遍历每个元素,判断是奇数就放到数组的头部。但是时间复杂度非常高,为 O(n²)。 18 | 19 | 2、优化思路:声明两个指针,分别指向头部和尾部,然后遍历。 20 | 21 | - 如果第一个指针指向的偶数,则继续移动第二个指针,直到遇到是奇数时,两者进行交换。 22 | - 如果第一个指针指向的是奇数,第二个也是奇数,则移动第一个指针,直到遇到偶数,然后进行交换。 23 | - 第一个只要是偶数,第二个是奇数,就要进行交换。 24 | 25 | 26 | 27 | #### 二、测试用例 28 | 29 | - 偶数与奇数交错 —— 普通测试 30 | - 所有偶数都在奇数前边、所有奇数都在偶数前边、数组只有一个数字 —— 特殊测试 31 | - 空数组 —— 输入测试 32 | 33 | 34 | 35 | #### 三、代码编写 36 | 37 | - 参数:数组 38 | - 判断数组是否为空 39 | - 声明两个指针 40 | - 判断是否只有一个数字 41 | - 上方思路进行判断 42 | 43 | ```javascript 44 | const ReorderOddEvent = (arr)=>{ 45 | // 判断数组是否为空 46 | if(arr == null || arr.length == 0){ 47 | return []; 48 | } 49 | 50 | // 声明两个指针 51 | let index1 = 0; 52 | let index2 = arr.length - 1; 53 | 54 | // 判断是否只有一个数字 55 | if(index1 == index2){ 56 | return arr; 57 | } 58 | 59 | while(index1 !== index2){ 60 | // 判断第一个指针是否为奇数 61 | if(arr[index1] % 2 == 0){ // 偶数 62 | if(arr[index2] % 2 !==0 ){ // 奇数 63 | swap(arr,index1,index2) 64 | }else{ 65 | index2--; 66 | } 67 | }else{ 68 | // 奇数 69 | index1++; 70 | } 71 | } 72 | return arr; 73 | } 74 | // 交换函数 75 | const swap = (arr,index1,index2)=>{ 76 | let temp = arr[index1]; 77 | arr[index1] = arr[index2]; 78 | arr[index2] = temp; 79 | } 80 | // 测试用例 81 | let arr = [1] 82 | console.log(ReorderOddEvent(arr)) 83 | ``` 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /连续子数组的最大和.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/9/17 3 | Title: 连续子数组的最大和 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试题四十二: 10 | 11 | 输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n)。例如:{1,-2, 3, 10,-4, 7,2, -5}。 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 1、根据数组里数字特点寻找思路 18 | 19 | > 遍历数组开始从第一个数累加,判断累加后的结果和累加第三个之后和第三个数判断大小,如果小于第三个数,那么前边累加不会存在连续最大值。然后在当前值进行累加,如果当前累加值大于当前的最大连续值,就会被存储。 20 | 21 | 22 | 23 | 2、动态规划 24 | 25 | 26 | 27 | #### 二、测试用例 28 | 29 | - 输入的数组有正数也有负数 —— 普通测试 30 | - 输入的全是正数、输入的全是负数 —— 特殊测试 31 | - 输入空数组 —— 输入测试 32 | 33 | 34 | 35 | #### 三、代码实现 36 | 37 | ```javascript 38 | var flag = false; 39 | const FindGreatestSumOfSubArray = (arr)=>{ 40 | // 判断输入的数组是否为空 41 | if(arr == null || arr.length == 0){ 42 | flag = true; 43 | return 0; 44 | } 45 | 46 | flag = false; 47 | let currentNumber = 0; 48 | let greateSum = arr[0]; 49 | for(let i = 0;i < arr.length;i++){ 50 | // 判断当前累加值是否小于等于 0 51 | if(currentNumber <= 0){ 52 | currentNumber = arr[i]; 53 | }else{ 54 | currentNumber += arr[i]; 55 | } 56 | // 判断是否大于当前连续和的最大值 57 | if(currentNumber > greateSum){ 58 | greateSum = currentNumber; 59 | } 60 | } 61 | return greateSum; 62 | } 63 | 64 | // 测试用例 65 | let arr = [-1,-2,-3,-10,-4,-7,-2,-5] 66 | console.log(FindGreatestSumOfSubArray(arr)) 67 | ``` 68 | 69 | 70 | 71 | #### 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /重建二叉树.md: -------------------------------------------------------------------------------- 1 | --- 2 | Time:2019/8/26 3 | Title: 重建二叉树 4 | Author: 小鹿 5 | --- 6 | 7 | 8 | 9 | ## 面试七:重建二叉树 10 | 11 | 已知前序遍历为{1,2,4,7,3,5,6,8},中序遍历为{4,7,2,1,5,3,8,6},它的二叉树是怎么样的? 12 | 13 | 14 | 15 | #### 一、思路 16 | 17 | 根据前、中序遍历的特点,(根左右、左根右),先根据前序遍历确定根节点,然后在中序遍历知道该根节点的左右树的数量,反推出前序遍历中左子树的结点有哪些。根据该思路进行递归即可完成二叉树的重建。 18 | 19 | 20 | 21 | #### 二、测试用例 22 | 23 | - 完全二叉树、非完全二叉树 —— 普通测试 24 | - 只有左子节点二叉树,只有右子节点、只有一个结点的二叉树 —— 特殊二叉树测试 25 | - 空树、前序和中序不匹配 —— 输入测试 26 | 27 | 28 | 29 | #### 三、代码实现 30 | 31 | 1、参数:前序遍历数组、中序遍历数组。 32 | 33 | 2、判断输入值 —— 两个数组。 34 | 35 | 3、新建节点(对象)并将前序的一个数作为根节点。 36 | 37 | 4、根据前序遍历的根,找到中序遍历的根(for)。 38 | 39 | 5、分离出此根节点的左右子树的数量,也分别知道了左、右子树的前、中序。 40 | 41 | 6、递归 —— 返回值让节点(对象)的左右子树 42 | 43 | 7、返回该根节点(对象) 44 | 45 | ```javascript 46 | // 定义结点 47 | // class TreeNode{ 48 | // constructor(data){ 49 | // this.data = data; 50 | // this.left = null; 51 | // this.right = null; 52 | // } 53 | // } 54 | 55 | // 参数:前序遍历数组 ~ 中序遍历数组 56 | const reConstructBinaryTree = (pre, vin)=>{ 57 | // 判断前序数组和中序数组是否为空 58 | if(!pre || pre.length === 0 || !vin || vin.length === 0){ 59 | return; 60 | } 61 | // 新建二叉树的根节点 62 | var treeNode = { 63 | val: pre[0] 64 | } 65 | // 查找中序遍历中的根节点 66 | for(var i = 0; i < pre.length; i++) { 67 | if (vin[i] === pre[0]) { 68 | // 将左子树的前中序遍历分割开 69 | treeNode.left = reConstructBinaryTree(pre.slice(1, i+1), vin.slice(0, i)); 70 | // 将右子树的前中序遍历分割开 71 | treeNode.right = reConstructBinaryTree(pre.slice(i+1),vin.slice(i+1)); 72 | } 73 | } 74 | // 返回该根节点 75 | return treeNode; 76 | } 77 | 78 | let pre = [1,2,4,7,3,5,6,8]; 79 | let vin = [4,7,2,1,5,3,8,6]; 80 | console.log(reConstructBinaryTree(pre,vin)); 81 | ``` 82 | 83 | --------------------------------------------------------------------------------