二叉树算法题
-
二叉树层次遍历
//思路 特殊情况 ,根节点为null,深度为0,return 0 借助队列,队列储存每一层的所有节点, 先保存数组长度,用于控制依次遍历循环——遍历每一个节点保存值,该节点的所有子结点从队尾入队,访问过的节点从头出队 注意——需要先把width存下来,用于for循环的结束标志,因为for循环里面直接操作了queue,不能去都不敢动态获取 队列初始值为root,对每一层都要进行上述遍历——while循环控制,队列空代表叶节点这一层遍历完成,此时遍历结束,退出循环 每次循环开始前初始化一个curNodes储存该层所有节点,每次循环结束,将curNodes压入result var levelOrder = function(root) { if (root === null) return []; //空树 var result = [], queue = [root]; while (queue.length) { let width = queue.length; //需要先把width存下来,用于for循环,for循环里面直接操作了数组 let curNodes = []; for (let i = 0; i < width; i++) { let node = queue.shift(); curNodes.push(node.val); node.left ? queue.push(node.left) : ""; node.right ? queue.push(node.right) : ""; } result.push(curNodes); } return result; };
-
二叉树反向层次遍历
var levelOrderBottom = function(root) { if (root === null) return []; //空树 var result = [], queue = [root]; while (queue.length) { let width = queue.length; //需要先把width存下来,用于for循环,for循环里面直接操作了数组 let curNodes = []; for (let i = 0; i < width; i++) { let node = queue.shift(); curNodes.push(node.val); node.left ? queue.push(node.left) : ""; node.right ? queue.push(node.right) : ""; } result.unshift(curNodes);//从头部插入,先插入顶部的,后插入底部的额 } return result; };
-
先序遍历
//思路 和中序其他都相似,要先访问根节点要后访问左节点,所以在根节点入栈的时候加入result数组 特殊情况:根节点为空,返回空数组 利用栈来存放访问过的根节点——定义一个指针指向要访问的节点,先访问根节点。将遍历到的结点值存入result数组,结点存入栈中。 stack存放遍历过的根,左节点,以便回溯访问右节点 然后指针一直向下访问左节点 当根节点没有左节点时,将根节点退栈,然后指针指向右节点,访问右节点 直到栈为空而且指针为空,所有节点遍历完成,退出循环,返回结果 var preorderTraversal = function(root) { if (root === null) return []; //空树 var result = [], stack = []; //result储存结果,stack存放遍历过的根,左节点,以便回溯访问右节点 var p = root; //p指向当前遍历的节点 //当有未访问的右节点(stack不空)||还有左节点没有访问(p不空)时进入循环 while (stack.length != 0 || p != null) { //还有左节点没有访问(p不空) if (p != null) { result.push(p.val); //遍历根节点 stack.push(p); //把遍历过的节点放入stack保管 p = p.left; //访问左节点 } //左节点访问完 else { p = stack.pop().right; //栈顶节点退栈,访问右节点 } } return result; };
-
中序遍历
//思路 和前序其他都相似,左节点要先访问根节点要后访问,所以在根节点出栈的时候加入result数组 特殊情况:根节点为空,返回空数组 利用栈来存放访问过的根节点——定义一个指针指向要访问的节点,先访问根节点。,结点存入栈中。 stack存放遍历过的根,左节点,以便回溯访问右节点 然后指针一直向下访问左节点 当根节点没有左节点时,将根节点退栈,将遍历到的结点值存入result数组,然后指针指向右节点,访问右节点 直到栈为空而且指针为空,所有节点遍历完成,退出循环,返回结果 var inorderTraversal = function(root) { if (root === null) return []; //空树 var result = [], stack = []; //result储存结果,stack存放遍历过的根,左节点,以便回溯访问右节点 var p = root; //p指向当前遍历的节点 //当有未访问的右节点(stack不空)||还有左节点没有访问(p不空)时进入循环 while (stack.length != 0 || p != null) { //还有左节点没有访问(p不空) if (p != null) { stack.push(p); //把遍历过的节点放入stack保管 p = p.left; //访问左节点 } //左节点访问完 else { let node = stack.pop();//栈顶节点退栈, result.push(node.val); //遍历左节点-根节点 p = node.right; //访问右节点 } } return result; };
-
后序遍历
//思路 特殊情况:根节点为空,返回空数组 利用栈来存放访问过的根节点——定义一个指针指向要访问的节点,先访问根节点。,结点存入栈中。 stack存放遍历过的根,右节点,以便回溯访问左节点 然后指针一直向下访问右节点 当根节点没有右节点时,将根节点退栈,将遍历到的结点值存入result数组,然后指针指向右节点,访问右节点 直到栈为空而且指针为空,所有节点遍历完成,退出循环,返回结果 var postorderTraversal = function(root) { if (root === null) return []; //空树 var result = [], stack = []; //result储存结果,stack存放遍历过的根,左节点,以便回溯访问右节点 var p = root; //p指向当前遍历的节点 //当有未访问的左节点(stack不空)||还有右节点没有访问(p不空)时进入循环 while (stack.length != 0 || p != null) { //还有左节点没有访问(p不空) if (p != null) { stack.push(p); //把遍历过的节点放入stack保管 result.unshift(p.val); //从result数组头部插入根节点-右节点-左节点 //最后result顺序为左节点-右节点-根节点 p = p.right; //访问右节点 } //右节点访问完 else { p = stack.pop().left; //栈顶节点退栈,访问左节点 } } return result; };
-
重建二叉树
//思路:二叉树前序遍历第一个点为根节点,中序遍历顺序为先左子树然后根节点最后右子树。
所以先通过前序遍历找出根节点,然后将中序遍历分为左右子树两组,最后对于每个子树依次递归调用。 function reConstructBinaryTree(pre, vin) { if (pre.length === 0 || vin.length === 0) return null; // 前序/中序又一个为空,就返回空值 let root = new TreeNode(pre[0]); //新建节点,作为根节点 if (pre.length === 1) return root; //是叶节点直接返回root,不需要计算子树 //不是叶节点,先递归得到左右节点 let rootIdx = vin.indexOf(pre[0]); //根节点在中序的位置 root.left = reConstructBinaryTree( // 递归调用得到左子树的根节点 pre.slice(1, rootIdx + 1), vin.slice(0, rootIdx) ); root.right = reConstructBinaryTree( // 递归调用得到右子树的根节点 pre.slice(rootIdx + 1), vin.slice(rootIdx + 1) ); return root; }
-
二叉树镜像
//思路:先将根的左右节点互换,然后就是递归调用,对左右子树进行分别处理 function Mirror(root) { // write code here if(root==null) return null; //首先先将左右节点互换 var tmp = root.left; root.left=root.right; root.right=tmp; //递归 Mirror(root.left); Mirror(root.right); }
-
平衡二叉树
//在这里,由于我们是以递归的形式,所以,我们会先遍历左节点,再遍历右节点,最后再遍历根节点(后序遍历) 同时,我们判断左右树的高度差是否超过 1,如果是,则进行中断,返回 false;否则,继续递归。 最后,我们将遍历的结果与 -1 进行比较,返回 true 或者 false let root = { val: 3, left: { val: 9, left: null, right: null }, right: { val: 20, left: { val: 3, left: null, right: null }, right: { val: 7, left: null, right: { val: 2, left: null, right: null }, }, }, } var isBalanced = function(root) { let ergodic = function(root) { if (!root) { return 0; } let left = ergodic(root.left); if (left === -1) { return -1; } let right = ergodic(root.right); if (right === -1) { return -1; } return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1; } return ergodic(root) != -1; }; console.log(isBalanced(root));
-
二叉树深度
//题目: 输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 //思路 特殊情况 ,根节点为null,深度为0,return 0 递归法,递归法获取左右子树的深度, root的深度是左右子树的深度最大值+1,返回深度 /* function TreeNode(x) { this.val = x; this.left = null; this.right = null; } */ function TreeDepth(pRoot) { // write code here if (!pRoot) return 0; //根节点为空,深度为0 var left = TreeDepth(pRoot.left); //左深度等于左子树深度+1 var right = TreeDepth(pRoot.right); //右深度等于右子树深度+1 return 1 + Math.max(left, right); //该节点深度为左右深度中的max,返回 }
-
二叉树最大深度
//思路 我们判断 root 是否还存在 left 和 right:如果不存在,那么树到了最底层,终止递归;如果存在,那么 depth 深度 + 1,并且遍历它的左树和右树。 同时,我们在树存在 left 和 right 的时候,将 depth 和 longest 比较,把最深的树给记录下来。 返回最长深度 longest。 var maxDepth = function(root) { return root === null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; };
-
二叉树最小深度
//思路 假设我是一只蜘蛛,我在一颗大树最底下(根节点),开始往上爬。 每经过 1 米(1 个 val 节点),我就留下一个分身。 当我爬到最顶的时候,我就进行最后标记,并告诉分身,前面凉凉了,开始报数! 于是从我为 1 开始,一直到根节点的长度,就是这个分支的高度。 const root = { val: 3, left: { val: 9, left: null, right: null }, right: { val: 20, left: { val: 15, left: null, right: null }, right: { val: 7, left: null, right: null }, }, } var minDepth = function(root) { if (!root) { return 0; } if (!root.left) { return minDepth(root.right) + 1; } if (!root.right) { return minDepth(root.left) + 1; } return Math.min(minDepth(root.left), minDepth(root.right)) + 1; }; minDepth(root);
-
翻转二叉树
var invertTree = function(root) { if (!root) { return null; } return { val: root.val, left: invertTree(root.right) || null, right: invertTree(root.left) || null, } };
-
二叉树下一个节点
//题目 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 //思路 特殊情况 空节点 情况一,有右子节点,下一个访问右子树中最左边的第一个节点。通过while循环,寻找右子树的左叶子节点可以找到。 情况二,没有右子结点,有父节点,当前节点是父节点的左节点,下一个访问父节点 情况三,没有右子结点,有父节点,当前节点是父节点的右节点,此时父节点一下都已经访问完,下一个访问父节点的父节点中,满足从左边连接的第一个 情况二和情况三可以归为一类——父节点存在时,设一个指针cur指向pnode父节点, 如果pnode是cur的左节点,返回; 如果pnode是cur的右节点,pnode向上指, 如果退出循环,查找到根节点都没有返回,就表示没有满足条件的节点,返回null function GetNext(pNode) { // write code here if (!pNode) return null; //空节点 var cur = null; //有右节点,下一个访问右子树里的最左边节点 if (pNode.right) { cur = pNode.right; while (cur.left) { cur = cur.left; } return cur; } //没有右节点,有父节点, while (pNode.next) { cur = pNode.next; //cur指向父节点 //如果pnode是cur的左节点,下一个访问的就是cur if (cur.left === pNode) return cur; //如果pnode不是cur的左节点,pnode指向父节点,向上继续查找 pNode = cur; } return null; //查找到根节点也没有符合条件的节点 }
-
最大二叉树
//题目: 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下: 二叉树的根是数组中的最大元素。 左子树是通过数组中最大值左边部分构造出的最大二叉树。 右子树是通过数组中最大值右边部分构造出的最大二叉树。 通过给定的数组构建最大二叉树,并且输出这个树的根节点。 //思路 递归法实现构建二叉树 递归出口,数组长度为0,return null,返回空节点 根节点为传入数组的max值, 左节点为递归调用,传入以max为分割点的左边数组的返回值, 右节点为递归调用,传入以max为分割点的右边数组的返回值, slice(left,right)分割数组,索引为left的元素划分进去,索引为right的元素不划分进去。 var constructMaximumBinaryTree = function(nums) { if (nums.length <= 0) return null; let max = Math.max(...nums); let i = nums.indexOf(max); let root = new TreeNode(max); root.left = constructMaximumBinaryTree(nums.slice(0, i)); root.right = constructMaximumBinaryTree(nums.slice(i + 1)); return root; };
-
序列化二叉树
//题目描述 请实现两个函数,分别用来序列化和反序列化二叉树 //思路: 序列化,将节点值存入数组中,空节点则使用特殊标记存入数组中。 反序列化,从数组中获取元素,为number类型则生成节点,为特殊标记,则为空节点 var arr=[]; function Serialize(pRoot) { // write code here if(pRoot==null){ arr.push('#') return; } arr.push(pRoot.val); Serialize(pRoot.left) Serialize(pRoot.right) } function Deserialize(s) { // write code here if(arr==null){ return null; } if(arr.length<1){ return null; } var root=null; var temp=arr.shift(); if(typeof temp=='number'){ root=new TreeNode(temp); root.left=Deserialize(arr); root.right=Deserialize(arr); } return root; }
-
二叉树中和为某一值的路径
//思路 因为要先访问最长的路径,所以考虑` 递归深度遍历:对根节点调用递归函数 curPath保存当前路径,sum保存当前和,每访问一个节点,更新路径和和, 更新之后,判断是否满足叶节点+和为指定值,如果满足,压入结果数组 注意:因为 curPath是一个引用类型,它的值一直在变化,所以压入数组时需要先进行深拷贝,如果不拷贝会导致结果输出为最后的curPath空数组。 左右节点存在时,对左右节点递归遍历 当前节点和所有子节点遍历完成,当前节点退出路径 function FindPath(root, expectNumber) { // write code here if (!root) return []; var res = [], //所有满足条件路径 curPath = [], //当前路径 p = root, //访问指针 sum = 0; //当前路径和 dfs(p, curPath, sum, res, (exp = expectNumber)); return res; } function dfs(p, curPath, sum, res, exp) { if(!p&&curPath.length===0) return;//所有节点访问完毕,退出递归 curPath.push(p.val); //节点值加入路径 sum += p.val; //更新和 if (!p.left && !p.right && sum === exp) { //栈顶节点是叶节点且路径满足条件 res.push(curPath.slice(0)); //路径加入结果 } p.left ? dfs(p.left, curPath, sum, res, exp) : ""; p.right ? dfs(p.right, curPath, sum, res, exp) : ""; curPath.pop(); //节点和所有子树访问完毕,退出路径 }
-
求根到叶子节点数字之和
//题目: 给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。
例如,从根到叶子节点路径 1->2->3 代表数字 123。计算从根到叶子节点生成的所有数字之和。 //方法一:递归法 //思路 递归输入当前节点和上一层的和,初始为root和0 如果节点空返回0 更新sum,是叶节点的话直接返回这个值 不是叶节点再加上左右节点的递归返回值 var sumNumbers = function(root) { return dfs(root,0); }; var dfs = function(p,sum) { if(!p) return 0; sum=sum*10+p.val; if(!p.left&&!p.right) return sum; return dfs(p.left,sum)+dfs(p.right,sum); };
-
左叶子之和
const sumOfLeftLeaves = (root, left) => { if (!root) { return 0; } if (!root.left && !root.right && left) { return root.val; } return sumOfLeftLeaves(root.left, true) + sumOfLeftLeaves(root.right); };
-
二叉树所有路径
//思路: 使用递归方法来解决此问题。对于根节点,如果根节点为空,则返回空数组[],如果为叶子节点,则返回包含此节点的值的数组(这个数组只有一个元素)。
若不为空也不是叶子节点,则对左右子树递归调用,将两个结果数组拼接起来,最后使用 map 函数来对数组的每个元素进行字符串的操作。 /** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ var binaryTreePaths = function(root) { if (root === null) return []; if (root.left === null && root.right === null) { return [root.val.toString()]; } var left = binaryTreePaths(root.left), right = binaryTreePaths(root.right); return left.concat(right).map(x => root.val + '->' + x); };
-
树的子结构
//题目描述 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构) //思路:比较B是不是A的子树,B是不是A的右子树的子树,B是不是A的左子树的子树。如果根元素相同,则开始判断左子树和右子树 function isSubtree(pRoot1,pRoot2){ if (pRoot2 == null) return true;//pRoot2为null,表示子树已经遍历完 if (pRoot1 == null) return false; if(pRoot1.val==pRoot2.val){ return isSubtree(pRoot1.left,pRoot2.left) && isSubtree(pRoot1.right,pRoot2.right); }else{ return false; } } function HasSubtree(pRoot1, pRoot2) { // write code here if(pRoot1==null||pRoot2==null) return false; return isSubtree(pRoot1,pRoot2)||HasSubtree(pRoot1.left,pRoot2)||HasSubtree(pRoot1.right,pRoot2); }
-
二叉搜索树最近公共祖先
//思路: 首先确保p的值小于q,若不是,则互换。这样有助于判断root、p、q三者的位置。 三种情况 root最小,说明p和q的最近公共祖先一个在root的右边,使用root.right递归调用 root在中间,说明最近公共祖先只能是root,返回root root最大,说明p和q的最近公共祖先一个在root的左边,使用root.left递归调用 /** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ var lowestCommonAncestor = function(root, p, q) { if (p.val > q.val) [p, q] = [q, p]; // 让p小于q,方便判断 if (root.val >= p.val && root.val <= q.val) { return root; } else if (root.val <= p.val && root.val <= q.val) { return lowestCommonAncestor(root.right, p, q); } else if (root.val >= p.val && root.val >= q.val) { return lowestCommonAncestor(root.left, p, q); } };
-
二叉搜索树的后序遍历
//思路 后续遍历我们可以知道,最右边的是根节点r。 2.通过根节点r我们可以判断左子树和右子树。 3.判断左子树中的每个值是否小于r,右子树的每个值是否大于r. 4.对左、右子树递归判断。 function VerifySquenceOfBST(sequence) { // write code here if(sequence.length<=0) return; return test(sequence,0,sequence.length-1) } function test(sequence,start,end){ if(start>=end) return true; var i=end-1; while(i>=start && sequence[i]>sequence[end]){ i--; } for(var j=i;j>=start;j--){ if(sequence[j]>sequence[end]){ return false; } } return test(sequence,start,i)&&test(sequence,i+1,end-1) }
-
二叉搜索树的第k个结点
//思路:二叉搜索树,若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;任意节点的左、右子树也分别为二叉查找树; 所以采用中序遍历的方法,遍历后的结果就是从小到大顺序的结果 function KthNode(pRoot, k) { // write code here var arr=[]; if(pRoot===null||k<1){ return null; } function midInorder(root){ if(root.left!==null){ midInorder(root.left); } arr.push(root); if(root.right!==null){ midInorder(root.right); } } midInorder(pRoot); return arr[k-1]; }
-
将有序数组转换为二叉搜索树
var sortedArrayToBST = function(nums) { if (!nums.length) return null; let mid = Math.floor(nums.length / 2); let root = { val: nums[mid], left: sortedArrayToBST(nums.slice(0, mid)), right: sortedArrayToBST(nums.slice(mid + 1)), } return root; };
-
深度优先遍历
该方法是以纵向的维度对dom树进行遍历,从一个dom节点开始,一直遍历其子节点,直到它的所有子节点都被遍历完毕之后在遍历它的兄弟节点。
//递归 function deepFirstSearch(node,nodeList) { if (node) { nodeList.push(node); var children = node.children; for (var i = 0; i < children.length; i++) //每次递归的时候将 需要遍历的节点 和 节点所存储的数组传下去 deepFirstSearch(children[i],nodeList); } return nodeList; }
//deepFirstSearch接受两个参数,第一个参数是需要遍历的节点,第二个是节点所存储的数组,并且返回遍历完之后的数组,该数组的元素顺序就是遍历顺序,调用方法:
let root = document.getElementById('root')
deepTraversal(root,nodeList=[])
//非递归 function deepFirstSearch(node) { var nodes = []; if (node != null) { var stack = []; stack.push(node); while (stack.length != 0) { var item = stack.pop(); nodes.push(item); var children = item.children; for (var i = children.length - 1; i >= 0; i--) stack.push(children[i]); } } return nodes; }
-
广度优先遍历
该方法是以横向的维度对dom树进行遍历,从该节点的第一个子节点开始,遍历其所有的兄弟节点,
再遍历第一个节点的子节点,完成该遍历之后,暂时不深入,开始遍历其兄弟节点的子节点。 //非递归版本 function breadthFirstSearch(node) { var nodes = []; if (node != null) { var queue = []; queue.unshift(node); while (queue.length != 0) { var item = queue.shift(); nodes.push(item); var children = item.children; for (var i = 0; i < children.length; i++) queue.push(children[i]); } } return nodes; }