JS資料結構第六篇 --- 二叉樹力扣練習題
1、第226題:翻轉二叉樹
遞迴+迭代兩種實現方式:
/** 反轉二叉樹 * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ /** * @param {TreeNode} root * @return {TreeNode} * 第一種方式迭代 * 執行用時 :72 ms, 在所有 JavaScript 提交中擊敗了87.29%的使用者 * 記憶體消耗 :33.8 M, 在所有 JavaScript 提交中擊敗了24.26%的使用者 */ var invertTree = function(root) { if (!root) return root; var arr = [root]; while(arr.length){ var current = arr.shift(); //取出節點,交換左右子節點 var temp = current.right; current.right = current.left; current.left = temp; //將左右子節點push到陣列中 if (current.right) arr.push(current.right); if (current.left) arr.push(current.left); } return root; }; /** * 第二種方式遞迴 * @param root * @returns {*} * 執行用時 :64 ms, 在所有 JavaScript 提交中擊敗了98.02%的使用者 * 記憶體消耗 :33.6 MB, 在所有 JavaScript 提交中擊敗了53.85%的使用者 */ var invertTree2 = function(root) { if (!root) return root; var temp = invertTree(root.left); root.left = invertTree(root.right); root.right = temp; return root; };
2、第144題:二叉樹的前序遍歷
初看這個題目描述,沒怎麼看懂,特別是控制檯的輸入輸出
比如輸入:[3, 9, 20, 15, 7, 88, 16, 2, 19, 13, 26, 11]
輸出是:[3,9,15,2,19,7,13,26,20,88,11,16]
一時沒弄明白,後面琢磨了一下,才發現力扣這裡的輸入是按照輸入順序來組成樹的,而不是按輸入的大小組成樹。
即上面這個輸入的數字列表,做成二叉樹圖為:
如果輸入的數字列表中帶有null, 則null所在的子樹空不佔位,
比如輸入:[3, 9, null, 20, 15, 7, 88, 16, 2, 19, null, 13, 26, 11]
輸出為:[3, 9, 20, 7, 19, 88, 13, 26, 15, 16, 11, 2]
輸入數字的二叉樹圖為:
理解了力扣題目的輸入輸出邏輯,咱們再做題,二叉樹的前序遍歷遞迴+迭代方式code (先根節點,再左子節點,再右子節點):
/** 前序遍歷規則:先根節點,再左子節點,再右子節點 * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ /** 第一種方式:遞迴 * @param {TreeNode} root * @return {number[]} 執行用時 :72 ms, 在所有 JavaScript 提交中擊敗了86.38%的使用者 記憶體消耗 :33.8 MB, 在所有 JavaScript 提交中擊敗了17.62%的使用者 */ var preorderTraversal = function(root) { var arr = []; recusion(root, arr); return arr; function recusion(root){ if (!root) return; //前序遍歷,先根節點,再左節點,再右節點 arr.push(root.val); recusion(root.left, arr); recusion(root.right, arr); } }; // function TreeNode(val) { // this.val = val; // this.left = this.right = null; // } /** * 第二種方式:迭代 * 執行用時 :76 ms, 在所有 JavaScript 提交中擊敗70.96%的使用者 * 記憶體消耗 :33.6 MB, 在所有 JavaScript 提交中擊敗了60.62%的使用者 */ var preorderForeach = function(root) { var res = []; if (!root) return res; var arr = [root]; while(arr.length){ //藉助於棧的特性:後進先出 var current = arr.pop(); res.push(current.val); //先將右節點壓入棧底,因為右節點後取值 if (current.right){ arr.push(current.right); } //左節點先取值,壓入棧頂 if (current.left){ arr.push(current.left); } } return res; };
3、第94題:二叉樹的中序遍歷
二叉樹中序遍歷,先找到最左邊的左子節點,從這裡開始,然後左子節點, 再根節點,再右子節點:
/** 中序遍歷:從小到大,從做左邊的左子節點,最後一個是右邊的右子節點 * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ /** 中序遍歷:按照從小到大排序,先找到最左邊的子節點,也就是最小值,再依次往上走父節點,右節點 * @param {TreeNode} root * @return {number[]} * 第一種方式:遞迴 * 執行用時 :64 ms, 在所有 JavaScript 提交中擊敗了97.67%的使用者 * 記憶體消耗 :33.8 MB, 在所有 JavaScript 提交中擊敗20.52%的使用者 */ var inorderTraversal = function(root) { const res = []; if (!root) return res; recusion(root); return res; function recusion(root){ if (!root) return; recusion(root.left); res.push(root.val); recusion(root.right); } }; /** * 第二種方式:迭代 * 執行用時 :68 ms, 在所有 JavaScript 提交中擊敗了94.67%的使用者 * 記憶體消耗 33.7 MB, 在所有 JavaScript 提交中擊敗了30.60%的使用者 */ var inorderTraversal2 = function(root) { const res = []; if (!root) return res; const arr = []; while (root || arr.length){ while(root){ arr.push(root); root = root.left; } root = arr.pop(); //最後一個左節點 res.push(root.val); root = root.right; } return res; };View Code
4、第145題:二叉樹的後序遍歷
後序遍歷的規則:先葉子節點,再根節點;即先左子節點,再右子節點,再根節點。
/** 後序遍歷規則:先葉子節點,葉子節點先左後右,再根節點 * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ /** 後序遍歷:先葉子節點,再左子樹,再右子樹 * 第一種方式:遞迴 * @param {TreeNode} root * @return {number[]} * 執行用時 :76 ms, 在所有 JavaScript 提交中擊敗了68.85%的使用者 * 記憶體消耗 :33.9 MB, 在所有 JavaScript 提交中擊敗了9.84%的使用者 */ var postorderTraversal = function(root) { var res = []; if (!root) return res; recusion(root); return res; function recusion(root){ if (!root) return; recusion(root.left); recusion(root.right); res.push(root.val); } }; /** * 第二種方式:迭代 * @param root * @returns {Array} * 執行用時 :80 ms, 在所有 JavaScript 提交中擊敗了48.15%的使用者 * 記憶體消耗 :33.7 MB, 在所有 JavaScript 提交中擊敗25.41%的使用者 */ var postorderTraversal = function(root) { var res = []; if (!root) return res; var arr = [root]; while (arr.length){ var current = arr.pop(); res.unshift(current.val); if (current.left){ arr.push(current.left); } if (current.right){ arr.push(current.right); } } return res; };View Code
5、第102題:二叉樹的層級遍歷
遞迴層級遍歷和前序遍歷差不多,迭代方式層級遍歷有點繞
/** 層次遍歷 * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } * 給定二叉樹: [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回其層次遍歷結果: [ [3], [9,20], [15,7] ] */ /** 層次遍歷,第一種方式:遞迴, 和前序遍歷差不多 * @param {TreeNode} root * @return {number[][]} * 執行用時 :84 ms, 在所有 JavaScript 提交中擊敗了55.19%的使用者 * 記憶體消耗 :34.6 M, 在所有 JavaScript 提交中擊敗了53.23%的使用者 */ var levelOrder = function(root) { var res = []; if (!root) return res; recusion(root, 0); return res; function recusion(root, level){ if (!root) return; if (res[level]){ res[level].push(root.val); } else{ res[level] = [root.val]; } if (root.left){ recusion(root.left, level+1); } if (root.right){ recusion(root.right, level+1); } } }; /** * 第二種層序遍歷:迭代 * @param root * @returns {Array} * 執行用時 :80 ms, 在所有 JavaScript 提交中擊敗了73.64%的使用者 * 記憶體消耗 :34.8 MB, 在所有 JavaScript 提交中擊敗了28.36%的使用者 */ var levelOrder2 = function(root) { var res = []; if (!root) return res; var queue = [root]; while(queue.length){ //內迴圈把這一層級的所有節點都放入tempQueue佇列中,每一個外迴圈則是每一層級重新開始 var arr = [], tempQueue = []; while(queue.length){ var current = queue.shift(); arr.push(current.val); if (current.left){ tempQueue.push(current.left); } if (current.right){ tempQueue.push(current.right); } console.log("tempQueue.length: ", tempQueue.length, ", queue.length: ", queue.length); console.log("-----------") } res.push(arr); queue = tempQueue; console.log(JSON.stringify(res)) console.log("***************************") } return res; }; // function TreeNode(val){ // this.val = val; // this.left = this.right = null; // } // // var node = new TreeNode(23); // node.left = new TreeNode(16); // node.right = new TreeNode(45); // node.left.left = new TreeNode(3); // node.left.right = new TreeNode(22); // node.right = new TreeNode(45); // node.right.left = new TreeNode(37); // node.right.right = new TreeNode(99); // console.log(levelOrder2(node));View Code
6、第104題:二叉樹的最大深度
二叉樹的最大深度和求二叉樹的層級遍歷差不多
/** 二叉樹的最大深度 * 給定一個二叉樹,找出其最大深度。 二叉樹的深度為根節點到最遠葉子節點的最長路徑上的節點數。 說明: 葉子節點是指沒有子節點的節點。 * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ /** * @param {TreeNode} root * @return {number} * 第一種方式:遞迴 * 執行用時 :96 ms, 在所有 JavaScript 提交中擊敗了51.18%的使用者 * 記憶體消耗 :37.1 MB, 在所有 JavaScript 提交中擊敗了40.00%的使用者 */ var maxDepth = function(root) { if (!root) return 0; var maxLevel = 1; recusion(root, 1); return maxLevel; function recusion(root, level){ if (level > maxLevel) { maxLevel = level; } if (root.left){ recusion(root.left, level+1); } if (root.right){ recusion(root.right, level+1); } } }; /** * 第二種:迭代 * @param root * @returns {number} 執行用時 :88 ms, 在所有 JavaScript 提交中擊敗了81.91%的使用者 記憶體消耗 :36.8 MB, 在所有 JavaScript 提交中擊敗了93.61%的使用者 */ var maxDepth2 = function(root) { if (!root) return 0; var level = 0, queue = [root]; while(queue.length){ var tempQueue = []; //內迴圈,每次把整個層級節點遍歷完, tempQueue儲存每個層級的所有節點 while(queue.length){ var current = queue.shift(); if (current.left){ tempQueue.push(current.left); } if (current.right){ tempQueue.push(current.right) } } level++; queue = tempQueue; } return level; }; // function TreeNode(val){ // this.val = val; // this.left = this.right = null; // } // // var node = new TreeNode(3); // node.left = new TreeNode(9); // node.right = new TreeNode(20); // node.right.left = new TreeNode(15); // node.right.right = new TreeNode(7); // console.log(maxDepth(node))View Code
7、第662題:二叉樹最大寬度
這個題和二叉樹層級遍歷/求最大深度類似,但比層級遍歷要繞要麻煩點。
迭代方式:迭代遍歷,用2個棧,一個用來儲存每一層級的節點,另一個棧用來儲存每個節點的編號。
對節點進行編號,規則:根節點編號從0開始,左子節點編號 = 父節點編號 * 2 + 1, 右子節點編號 = 父節點編號 * 2 + 2;
遞迴方式:規則同迭代方式,也是對節點進行編號
如圖:
/** * 給定一個二叉樹,編寫一個函式來獲取這個樹的最大寬度。樹的寬度是所有層中的最大寬度。這個二叉樹與滿二叉樹(full binary tree)結構相同,但一些節點為空。 每一層的寬度被定義為兩個端點(該層最左和最右的非空節點,兩端點間的null節點也計入長度)之間的長度。 示例 1: 輸入: 1 / \ 3 2 / \ \ 5 3 9 輸出: 4 解釋: 最大值出現在樹的第 3 層,寬度為 4 (5,3,null,9)。 示例 2: 輸入: 1 / 3 / \ 5 3 輸出: 2 解釋: 最大值出現在樹的第 3 層,寬度為 2 (5,3)。 示例 3: 輸入: 1 / \ 3 2 / 5 輸出: 2 解釋: 最大值出現在樹的第 2 層,寬度為 2 (3,2)。 示例 4: 輸入: 1 / \ 3 2 / \ 5 9 / \ 6 7 輸出: 8 解釋: 最大值出現在樹的第 4 層,寬度為 8 (6,null,null,null,null,null,null,7)。 * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ /** * @param {TreeNode} root * @return {number} * 第一種方式:遞迴 * 執行用時 :84 ms, 在所有 JavaScript 提交中擊敗了100.00%的使用者 * 記憶體消耗 :36.7 MB, 在所有 JavaScript 提交中擊敗了37.50%的使用者 */ var widthOfBinaryTree = function(root) { if (!root) return 0; //queue儲存節點,numArr儲存節點對應的節點編號位置 var queue = [root], numArr = [0], maxWidth = 1; while (queue.length) { //tempQueue儲存每一層級所有的節點,tempNumArr儲存對應節點的編號位置 var tempQueue = [], tempNumArr = []; while (queue.length) { var node = queue.shift(), num = numArr.shift(); //取出棧底節點和編號 if (node.left) { tempQueue.push(node.left); tempNumArr.push(num * 2 + 1); } if (node.right) { tempQueue.push(node.right); tempNumArr.push(num * 2 + 2); } } var tempWidth = 0; //計算tempNumArr中儲存的這一層的寬度, 最後一位元素儲存這一層級最大寬度的編號 if (tempNumArr.length) { tempWidth = tempNumArr[tempNumArr.length - 1] - tempNumArr[0] + 1; } if (tempWidth > maxWidth) { maxWidth = tempWidth; //更新最大寬度 } //開始下一個層級的寬度計算 queue = tempQueue; numArr = tempNumArr; } return maxWidth; }; /** * 第二種遞迴方式: * @param root * @returns {number} * 執行用時 :84 ms, 在所有 JavaScript 提交中擊敗了100.00%的使用者 * 記憶體消耗 :36 MB, 在所有 JavaScript 提交中擊敗了75.00%的使用者 */ var widthOfBinaryTree2 = function(root) { if (!root) return 0; var res = [], maxWidth = 1; recusion(root, 0, 0); return maxWidth; function recusion(root, level, num){ if (res[level]){ res[level].push(num); } else{ res[level] = [num]; } //計算最大寬度 var tempArr = res[level]; var tempWidth = tempArr[tempArr.length - 1] - tempArr[0] + 1; if (tempWidth > maxWidth) { maxWidth = tempWidth; } if (root.left){ recusion(root.left, level + 1, num * 2 + 1); } if (root.right){ recusion(root.right, level + 1, num * 2 + 2); } } }; // function TreeNode(val){ // this.val = val; // this.left = this.right = null; // } // // //[1,1,1,1,null,null,1,1,null,null,1] // var root = new TreeNode(1); // root.left = new TreeNode(1); // root.right = new TreeNode(1); // root.left.left = new TreeNode(1); // root.left.right = new TreeNode(3); // root.right.right = new TreeNode(9); // console.log(widthOfBinaryTree(root));View Code
&n