二叉樹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) }