二叉树node
在此以前,先来回顾一下二叉搜索树的建立和基本方法
掌握了如下代码,将能熟练地对二叉树更进一步的操做面试
var Node=function(key){ this.key=key; this.left=null; this.right=null; } class BinaryTree{ constructor(){ this.root=null; } // 插入节点(按二叉搜索树要求) insert(key){ let newNode=new Node(key); if(this.root==null){ this.root=newNode; }else{ this.insertNode(this.root,newNode); } } insertNode(node,newNode){ if(newNode.key<node.key){ if(node.left==null){ node.left=newNode; }else{ this.insertNode(node.left,newNode); } }else{ if(node.right==null){ node.right=newNode; }else{ this.insertNode(node.right,newNode); } } } //前序遍历 DLR(callback){ this.traverseNodesDLR(this.root,callback); } traverseNodesDLR(node,callback){ if(node!=null){ callback(node.key); this.traverseNodesDLR(node.left,callback); this.traverseNodesDLR(node.right,callback) } } // 中序遍历 LDR (callback){ this.traverseNodesLDR(this.root,callback); } traverseNodesLDR=function (node,callback){ if(node){ this.traverseNodesLDR(node.left,callback); callback(node.key); this.traverseNodesLDR(node.right,callback); } } // 查找最小值 min(){ let current=this.root; while(current.left!=null){ current=current.left; } return current.key; } // 查找最大值 max=function(){ let current=this.root; while(current.right!=null){ current=current.right; } return current.key; } //删除节点 removeNode = function(node,key){ if(node ===null){ return null } if(key<node.key){ node.left=this.removeNode(node.left,key); return node; }else if(key>node.key){ node.right = this.removeNode(node.right,key); return node; }else{ //删除节点 //一、删除没有左右子树的节点 if(node.left ===null && node.right ===null){ node =null; return node; } //二、删除只有右子树的节点 if(node.left === null){ node = node.right; return node; } //三、删除只有左子树的节点 if(node.right === null){ node = node.left; return node; } //四、删除左右子树都有的节点 //4.1查找右子树中最小的节点N, var minNode = getMinNode(node.right); //4.2用N替换须要删除的节点, node.key = minNode.key; //4.3删除右子树最小的节点 node.right =this. removeNode(node.right,minNode.key); return node } } deleteNode = function(key){ this.removeNode(this.root,key); } } var callback=item=>console.log(item);
下面的题目全都来自leetcode,直接leetcode搜名字能够找到。 属于比较常见的二叉树相关算法,难度:简单/中等
代码均为简单易于理解的版本,效率还不错算法
首先要掌握二叉树的遍历方法 主要是两种方法,递归和迭代数组
递归很简单,面试常考迭代函数
// 1.递归 var preorderTraversal = function(root) { let res = [] let traversal = function(root) { if(!root) return res.push(root.val) traversal(root.left) traversal(root.right) } traversal(root) return res }; // 2.迭代 显式维护一个栈 var preorderTraversal = (root)=>{ let res = [] if(!root) return res let stack = [root] while(stack.length) { let top = stack.pop() res.push(top.val) if(top.right) stack.push(top.right) // 右节点先入栈 if(top.left) stack.push(top.left) } return res }
// 中序遍历 迭代 var inorderTraversal = (root) => { let res = [] if(!root) return res let stack = [root] while(stack.length||root) { let top = stack[stack.length-1] while(root) { stack.push(root) root = root.left } let root = stack.pop() res.push(root.val) if(root.right) stack.push(root.right) } return res } /// 迭代 var inorderTraversal = function(root) { const res = []; const stk = []; while (root || stk.length) { while (root) { stk.push(root); root = root.left; } root = stk.pop(); res.push(root.val); root = root.right; // 对它中序遍历 } return res; };
递归的方式很简单,跟前序中序相似,这里再也不赘述post
var postorderTraversal = function(root) { let res = [],stack = [] while(root||stack.length) { res.unshift(root.val) // 往前插入 if(root.left) stack.push(root.left) if(root.right) stack.push(root.right) root = stack.pop() } return res };
var searchBST = function(root, val) { if(root==null) return null; if(root.val==val) return root; if(root.val<val){ /////////这里不加return返回了undefined //外层函数没法收到返回值 return searchBST(root.right,val) }else { return searchBST(root.left,val) } };
思路 二叉搜索树的中序遍历时作指针移动操做ui
var convertBiNode = function(root){ if(!root) return null let p=new TreeNode('head') //哨兵节点 let curr=p //始终指向最新的节点 var LDR=function(node){ if(!node) return null LDR(node.left) //指针移动 node.left=null //当前节点的左节点置空,不指向其余节点 curr.right=node //链接 curr=curr.right LDR(node.right) } LDR(root) return p.right }
//没有头节点的循环链表 var treeToDoublyList = function(root) { if(!root) return let head=null p=head //p用于遍历 LDR(root) p.right=head //最后首尾的处理 head.left=p return head function LDR(node){ //放到外部传参的话不能改变p if(!node) return LDR(node.left) { if(p){ p.right=node }else{ //最左边节点 head=node } node.left=p p=node } LDR(node.right) } };
var isBalanced = function(root) { if(judge(root)==-1) return false return true }; function judge(root){ if(!root){ return 0 } let left=judge(root.left) if(left==-1) //该节点左孩子的左右子树不平衡,直接将该节点也返回不平衡 return -1 let right=judge(root.right) if(right==-1) return -1 if (Math.abs(left-right)>1) return -1 return Math.max(left,right)+1 }
关键:出队时左右孩子入队指针
/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ var levelOrder=function(root){ let result=[]; if(root==null) return result; let queue=[root]; //根节点入队 while(queue.length>0){ let arr=[]; //////根据题目要求,每层的放在一个数组。所以还需一个循环控制 层 let len=queue.length; while(len){ //遍历当前层 let node=queue.shift(); //删除并返回首元素 arr.push(node.val); if(node.left) queue.push(node.left) if(node.right) queue.push(node.right) len--; ///**易漏 } result.push(arr); } return result; //每层放在一个数组中 }
层序遍历时多了一步code
var largestValues = function(root) { if(!root) return [] let res=[] let queue=[root] while(queue.length){ let len=queue.length let tmp=[] while(len){ let t=queue.shift() if(t.left) queue.push(t.left) if(t.right) queue.push(t.right) tmp.push(t.val) len-- } res.push(Math.max(...tmp)) } return res };
///////////递归 var mirrorTree = function(root) { let change=function(node){ if(!node){ return } //这里加入对子节点的判断能提升效率 //避免对子节点的两个null交换 if(node.left||node.right){ let temp=node.left //用[ ]解构交换更快 node.left=node.right node.right=temp change(node.left) change(node.right) } } change(root) return root };
给定一个二叉树,检查它是不是镜像对称的。
//方法1. 递归 var isSymmetric = function(root) { if(!root) return true let isEqual=function(left,right){ if(!left&&!right) return true //到底了,确定为ture,不然中途会返回false if(!left||!right) return false //一空一不空 if(left.val!=right.val) return false //只剩值相等的状况,继续判断 return isEqual(left.left,right.right)&&isEqual(left.right,right.left) } return isEqual(root.left,root.right) }; //方法2. 层序遍历 var isSymmetric = (root) => { if (!root) return true let queue = [root.left, root.right] while (queue.length) { // 队列为空表明没有可入列的节点,遍历结束 let len = queue.length // 获取当前层的节点数 for (let i = 0; i < len; i += 2) { // 一次循环出列两个,因此每次+2*** let left = queue.shift() // 左右子树分别出列 let right = queue.shift() // 分别赋给left和right变量 if ((left && !right) || (!left && right)) return false // 不知足对称 if (left && right) { // 左右子树都存在 if (left.val !== right.val) return false // 左右子树的根节点值不一样 queue.push(left.left, right.right) // 让左子树的left和右子树的right入列 queue.push(left.right, right.left) // 让左子树的right和右子树的left入列 } } } return true // 循环结束也没有遇到返回false }
var maxDepth = function(root) { if(!root) return 0 let right=maxDepth(root.right) let left=maxDepth(root.left) return Math.max(left,right)+1 };
深度优先和广度优势
var maxDepth = function(root) { if(!root) return 0; if(!root.children) return 1; let max = 0; for(let i=0; i<root.children.length; i++) { let childDepth = maxDepth(root.children\[i\]); max = Math.max(max, childDepth) } return max + 1; };
2.广度优先 算出总共有几层便可
// 层序遍历,迭代 var maxDepth = function(root) { if(!root) return 0; let queue = \[\]; let level = 0; queue.push(root); while(queue.length) { let length = queue.length; while(length -- ) { let node = queue.shift(); node.children && (queue = queue.concat(node.children)); } level ++; } return level; };
var minDepth = function(root) { if(!root) return 0; let left=minDepth(root.left); let right=minDepth(root.right); if(!root.left||!root.right) return left+right+1; return Math.min(left,right)+1; //左右均不为空 };
var pathSum = function(root, sum) { let res = [] let dfs=function(root, path, sum){ if(!root) return sum -= root.val if(sum == 0 && !root.left && !root.right){ res.push([...path, root.val]) return } path.push(root.val) dfs(root.left, path, sum) dfs(root.right, path, sum) path.pop() } dfs(root, [], sum) return res };
都有节点时求和
var mergeTrees = function(t1, t2) { if(!t2) return t1 //t2空。t1是否空不重要 if(!t1) return t2 //t1空,t2不空 if(t1&&t2){ t1.val+=t2.val } t1.left=mergeTrees(t1.left,t2.left) t1.right=mergeTrees(t1.right,t2.right) return t1 //新树用t1返回 };
var sortedArrayToBST = function(nums){ if(nums.length==0){ return null } let m=Math.floor((nums.length-1)/2) let p=new TreeNode(nums[m]) p.left=sortedArrayToBST(nums.slice(0,m)) p.right=sortedArrayToBST(nums.slice(m+1)) return p };
(注意是二叉搜索树,容易肯定在哪一个子树上)
临界条件:
从左右子树分别进行递归,即查找左右子树上是否有p节点或者q节点
var lowestCommonAncestor = function(root, p, q) { if(!root||root===p||root===q) return root; let left = lowestCommonAncestor(root.left,p,q); let right = lowestCommonAncestor(root.right,p,q); if(left&& right) //分别在root的左右两边 return root; return left!= null ? left : right; //都在左子树或都在右子树上 };
//分治 每次判断左子树全部节点均小于当前根节点,右子树均大于当前根节点 // var verifyPostorder = function (postorder){ let len=postorder.length if(len<3) return true //长度0,1,2时,必定能构造出来知足条件的树 root=postorder[len-1] //划分,找第一个比根节点大的 let i=0; for(;i<len-1;i++){ if(postorder[i]>root) break } //右子树是否知足 let flag=true for(let j=i+1;j<len;j++){ if(postorder[j]<root) flag = false } ////注意这里的区间 if(flag) return verifyPostorder(postorder.slice(0,i))&&verifyPostorder(postorder.slice(i,len-1)) (else) return false }
var buildTree = function(preorder, inorder) { if(!preorder.length) return null //上个节点为叶节点,左右为null let node=new TreeNode(preorder[0]),i=0 for(;i<inorder.length;i++){ if(inorder[i]===node.val) break } node.left=buildTree(preorder.slice(1,1+i),inorder.slice(0,i)) node.right=buildTree(preorder.slice(1+i),inorder.slice(i+1)) return node };
题意:判断A是否是B的子树
var isSubStructure = function(A, B) { if(!A||!B) return false if(A.val===B.val&& find(A.left,B.left)&&find(A.right,B.right)) return true //根节点相同而且左右子树都相同(find递归) else{ return isSubStructure(A.left,B)||isSubStructure(A.right,B) } }; let find=function(A,B){ if(!B) return true if(!A) return false if(A.val!==B.val){ return false } return find(A.left,B.left)&&find(A.right,B.right) }