-
二叉樹層次遍歷
//思路 特殊情況 ,根節點為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; }