數據結構和算法的知識博大精深, 這裏只是對這幾種數據結構作一些簡單的介紹。並對leetcode上部分相關的簡單和中等題作出解答。還請各位看官見諒node
二叉樹是一種典型的樹狀結構, 二叉樹每個節點最多有兩個子樹的結構。如下是遍歷二叉樹的幾種方式, 總的來講使用遞歸的方式, 仍是很是好理解的。算法
前序遍歷首先訪問根節點,而後遍歷左子樹,最後遍歷右子樹數組
節點遍歷的順序: F, B, A, D, C, E, G, I, Hmarkdown
var preorderTraversal = function (root) { let result = [] const traversing = (node) => { // 結束遞歸 if (!node) return [] // 首先遍歷當前的節點 result.push(node.val) // 若是有左子樹優先遍歷左子樹 if (node.left) { result.concat(traversing(node.left)) } // 遍歷又子樹 if (node.right) { result.concat(traversing(node.right)) } } traversing(root) return result } 複製代碼
中序遍歷是先遍歷左子樹,而後訪問根節點,而後遍歷右子樹數據結構
節點遍歷的順序: A, B, C, D, E, F, G, H, I數據結構和算法
var inorderTraversal = function (root) { let result = [] const traversing = (node) => { if (!node) return // 優先遍歷左子樹 if (node.left) { traversing(node.left) } // 而後獲取當前的節點 if (node.val) { result.push(node.val) } // 而後遍歷右子樹 if (node.right) { traversing(node.right) } } traversing(root) return result } 複製代碼
先遍歷左子樹,而後遍歷右子樹,最後訪問樹的根節點post
節點遍歷的順序: A, C, E, D, B, H, I, G, Fui
var postorderTraversal = function (root) { let result = [] const traversing = (node) => { if (!node) return if (node.left) { traversing(node.left) } if (node.right) { traversing(node.right) } if (node.val) { result.push(node.val) } } traversing(root) return result }; 複製代碼
廣度優先搜索是一種普遍運用在樹或圖這類數據結構中,遍歷或搜索的算法。該算法從一個根節點開始,首先訪問節點自己。而後依次遍歷它的二級鄰節點、三級鄰節點,以此類推。咱們這裏依然使用遞歸遍歷, 可是咱們在遞歸中添加level參數用來肯定當前節點的層級。this
var levelOrder = function (root) { let result = [] const traversing = (node, level) => { if (!node) return if (!result[level]) result[level] = [] result[level].push(node.val) if (node.left) { traversing(node.left, level + 1) } if (node.right) { traversing(node.right, level + 1) } } traversing(root, 0) return result } 複製代碼
給定一個二叉樹,找出其最大深度。二叉樹的深度爲根節點到最遠葉子節點的最長路徑上的節點數。spa
對當前的二叉樹使用後序遍歷。若是當前節點沒有左子樹而且沒有右子樹, 說明這個節點是當前分支中最深的節點, 咱們記錄它自身的最大深度爲1(由於它自身沒有子節點)。若是當前節點有左子樹和右子樹, 咱們取左右子樹中最大的深度(由於是後序遍歷, 在遍歷當前根節點時, 左右樹已經被遍歷了)。取最大深度後加一就是當前節點的深度。
var maxDepth = function(root) { if (!root) return 0 const traversing = (node) => { if (!node) return if (!node.left && !node.right) { node.depth = 1 return } if (node.left) { traversing(node.left) } if (node.right) { traversing(node.right) } let max_left_depth = 0 let max_right_depth = 0 if (node.left) { max_left_depth = node.left.depth } if (node.right) { max_right_depth = node.right.depth } node.depth = Math.max(max_left_depth, max_right_depth) + 1 } traversing(root) return root.depth } 複製代碼
給定一個二叉樹,檢查它是不是鏡像對稱的
// 對稱二叉樹 1 / \ 2 2 / \ / \ 3 4 4 3 // 不是對稱二叉樹 1 / \ 2 2 \ \ 3 3 複製代碼
採用BFS遍歷, 獲取每一級的全部節點結果集, 不存在的子節點使用null代替。判斷每一級的節點是否能構成迴文字符串便可。
var isSymmetric = function(root) { // BFS遍歷 let result = [] const traversing = (node, level) => { if (!result[level]) result[level] = [] // 不存在的節點使用null填充 if (!node) { // 終止遞歸 return result[level].push('null') } else { result[level].push(node.val) } if (node.left) { traversing(node.left, level + 1) } else { traversing(null, level + 1) } if (node.right) { traversing(node.right, level + 1) } else { traversing(null, level + 1) } } traversing(root, 0) // 判斷每一級的結果可否構成迴文字符串 for (let i = 0; i < result.length - 1; i++) { if (result[i].join('') !== result[i].reverse().join('')) { return false } } return true }; 複製代碼
給定一個二叉樹和一個目標和,判斷該樹中是否存在根節點到葉子節點的路徑,這條路徑上全部節點值相加等於目標和。
// 給定目標sum = 22
// 5->4->11->2和爲22, 返回true
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
複製代碼
咱們採用前序遍歷, 每次遍歷使用目標減去當前節點的值,並將新的目標帶入下一次的遞歸中。若是當遍歷到最深處的節點,而且節點的值等於目標的值。說明二叉樹擁有路徑的和等於目標值。
var hasPathSum = function(root, sum) { let result = [] const traversing = (root, sum) => { if (!root) return false // 說明擁有路徑等於目標的和 if (!root.left && !root.right && root.val === sum) { result.push(root.val) } if (root.left) { traversing(root.left, sum - root.val) } if (root.right) { traversing(root.right, sum - root.val) } } traversing(root, sum) return result.length > 0 }; 複製代碼
根據一棵樹的中序遍歷與後序遍歷構造二叉樹。
// 中序遍歷 inorder = [9,3,15,20,7] // 後序遍歷 postorder = [9,15,7,20,3] // 構建結果 3 / \ 9 20 / \ 15 7 複製代碼
思路與從前序與中序遍歷序列構造二叉樹題相似,這裏不在贅述
var buildTree = function(inorder, postorder) { let binaryTree = {} const iteration = (postorder, inorder, tree) => { if (!postorder.length) { binaryTree = null return } tree.val = null tree.left = { val: null, left: null, right: null } tree.right = { val: null, left: null, right: null } // 前序遍歷第一個節點爲當前樹的根節點 let rootVal = postorder.splice(postorder.length - 1, 1)[0] // 中序遍歷根節點的索引 let rootIndex = inorder.indexOf(rootVal) // 中序遍歷的左子樹 let inorderLeftTree = inorder.slice(0, rootIndex) // 中序遍歷的右子樹 let inorderRightTree = inorder.slice(rootIndex + 1) // 前序遍歷的左子樹 let postorderLeftTree = postorder.slice(0, inorderLeftTree.length) // 前序遍歷的右子樹 let postorderRightTree = postorder.slice(inorderLeftTree.length) tree.val = rootVal if (postorderLeftTree.length === 1 || inorderLeftTree.length === 1) { tree.left.val = postorderLeftTree[0] } else if (postorderLeftTree.length > 1 || inorderLeftTree.length > 1) { iteration(postorderLeftTree, inorderLeftTree, tree.left) } else { tree.left = null } if (postorderRightTree.length === 1 || inorderRightTree.length === 1) { tree.right.val = postorderRightTree[0] } else if (postorderRightTree.length > 1 || inorderRightTree.length > 1) { iteration(postorderRightTree, inorderRightTree, tree.right) } else { tree.right = null } } iteration(postorder, inorder, binaryTree) return binaryTree } 複製代碼
本題依然採用遞歸的思路, 前序遍歷的第一個節點爲二叉樹的根節點,以此做爲突破口。
本題的前置條件是樹中不存在重複的元素。能夠由中序遍歷的結果以及根節點值獲取根節點的左子樹以及右子樹。
咱們這時能夠得到根節點左子樹和右子樹的長度。反過來能夠獲取前序遍歷結果中的左右子樹。咱們這時,把左右子樹再當成一顆二叉樹,使用遞歸的形式重複此過程。既能夠推導出整顆二叉樹。
var buildTree = function(preorder, inorder) { let binaryTree = {} const iteration = (preorder, inorder, tree) => { if (!preorder.length) { binaryTree = null return } tree.val = null tree.left = { val: null, left: null, right: null } tree.right = { val: null, left: null, right: null } // 前序遍歷第一個節點爲當前樹的根節點 let rootVal = preorder.splice(0, 1)[0] // 中序遍歷根節點的索引 let rootIndex = inorder.indexOf(rootVal) // 中序遍歷的左子樹 let inorderLeftTree = inorder.slice(0, rootIndex) // 中序遍歷的右子樹 let inorderRightTree = inorder.slice(rootIndex + 1) // 前序遍歷的左子樹 let preorderLeftTree = preorder.slice(0, inorderLeftTree.length) // 前序遍歷的右子樹 let preorderRightTree = preorder.slice(inorderLeftTree.length) tree.val = rootVal if (preorderLeftTree.length === 1 || inorderLeftTree.length === 1) { tree.left.val = preorderLeftTree[0] } else if (preorderLeftTree.length > 1 || inorderLeftTree.length > 1) { iteration(preorderLeftTree, inorderLeftTree, tree.left) } else { tree.left = null } if (preorderRightTree.length === 1 || inorderRightTree.length === 1) { tree.right.val = preorderRightTree[0] } else if (preorderRightTree.length > 1 || inorderRightTree.length > 1) { iteration(preorderRightTree, inorderRightTree, tree.right) } else { tree.right = null } } iteration(preorder, inorder, binaryTree) return binaryTree } 複製代碼
二叉搜索樹是二叉樹的一種特殊形式。 二叉搜索樹具備如下性質:每一個節點中的值必須大於(或等於)其左側子樹中的任何值,但小於(或等於)其右側子樹中的任何值。
對於二叉搜索樹,咱們能夠經過中序遍歷獲得一個遞增的有序序列
給定一個二叉樹,判斷其是不是一個有效的二叉搜索樹。
能夠經過中序DFS遍歷二叉搜索樹, 判斷遍歷的結果是否爲遞增的數組判斷是否爲搜索二叉樹
var isValidBST = function(root) { if (!root) return true // 中序DFS let result = [] const iteration = (root) => { if (root.left) { iteration(root.left) } result.push(root.val) if (root.right) { iteration(root.right) } } iteration(root) let resultString = result.join(',') let result2String = [...new Set(result.sort((a, b) => a - b))].join(',') return resultString === result2String }; 複製代碼
若是目標值等於節點的值,則返回節點, 若是目標值小於節點的值,則繼續在左子樹中搜索, 若是目標值大於節點的值,則繼續在右子樹中搜索。
// 遞歸就完事了 var searchBST = function(root, val) { if (!root) return null let result = null const seatch = (node) => { if (node.val === val) { return result = node } else if (val > node.val && node.right) { seatch(node.right) } else if (val < node.val && node.left) { seatch(node.left) } } seatch(root) return result }; 複製代碼
在二叉搜索樹中的插入操做和搜索操做相似。根據節點值與目標節點值的關係,搜索左子樹或右子樹。當節點沒有左右子樹時。判斷目標值和當前節點的關係,執行插入的操做。
var insertIntoBST = function(root, val) { const insert = (root) => { if (val > root.val) { if (root.right) { insert(root.right) } else { root.right = new TreeNode(val) } } else if (val < root.val) { if (root.left) { insert(root.left) } else { root.left = new TreeNode(val) } } } insert(root) return root }; 複製代碼
刪除二叉樹的節點的操做複雜度遠遠大於搜索和插入的操做。刪除搜索二叉樹節點時, 須要考慮多種狀態
刪除的節點沒有子節點的時候, 直接移除改節點(從它的父節點上移除)
刪除的節點只有一個子節點的時候, 須要將須要刪除的節點的父節點, 連接上刪除節點的子節點。便可完成刪除
刪除的節點有兩個子節點的時候, 須要將刪除節點右子樹中的最小值, 賦予刪除的節點。而後刪除右子樹中的最小值便可。
/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ /** * @param {TreeNode} root * @param {number} key * @return {TreeNode} */ var deleteNode = function(root, key) { // 根節點爲空的狀況 if (!root) { return null } if (!root.left && !root.right && root.val === key) { root = null return root } if (!root.left && root.right && root.val === key) { root = root.right return root } if (root.left && !root.right && root.val === key) { root = root.left return root } // 根節點替換的狀況 // 尋找當前樹的最小節點 const findMin = (root) => { let min = root while (min.left) { min = min.left } return min } let parentNode = null // 找到最近的父級 const searchParent = (node, searchValue) => { console.log('???') let current = node let breaker = false while (!breaker) { console.log('查找父親') if ( (current.left && searchValue === current.left.val) || (current.right && searchValue === current.right.val) ) { breaker = true } else if (searchValue < current.val) { current = current.left } else if (searchValue > current.val) { current = current.right } else { current = null } if (!current) break } parentNode = current } const remove = (node, deleteValue) => { if (node.val === deleteValue) { console.log('1') // node爲要刪除的節點 if (!node.left && !node.right) { console.log('3') // 若是沒有任何子節點 searchParent(root, node.val) if (parentNode.left && parentNode.left.val === deleteValue) { parentNode.left = null } else { parentNode.right = null } } else if (!node.left && node.right) { console.log('4') // 若是隻有一個子節點 searchParent(root, node.val) if (parentNode.left && parentNode.left.val === deleteValue) { parentNode.left = node.right } else { parentNode.right = node.right } } else if (node.left && !node.right) { console.log('5') // 若是隻有一個子節點 searchParent(root, node.val) if (parentNode.left && parentNode.left.val === deleteValue) { parentNode.left = node.left } else { parentNode.right = node.left } } else { console.log('6') // 若是有兩個子節點 // 找到右子樹中最小的節點 let minNode = findMin(node.right) console.log('7') let minNodeValue = minNode.val console.log('8') remove(root, minNodeValue) console.log('9') node.val = minNodeValue console.log('10') } } else if (deleteValue > node.val && node.right) { console.log('2') remove(node.right, deleteValue) } else if (deleteValue < node.val && node.left) { console.log('3') remove(node.left, deleteValue) } } remove(root, key) return root }; 複製代碼
給定一個二叉搜索樹, 找到該樹中兩個指定節點的最近公共祖先。
從根節點開始遍歷操做, 若是根節點的值大於目標節點1, 小於目標節點2。說明根節點就是最近的公共祖先。
若是根節點大於目標節點1, 目標節點2,則使用根節點的左子節點重複前一步的操做。
若是根節點小於目標節點1, 目標節點2,則使用根節點的右子節點重複前一步的操做。
/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ /** * @param {TreeNode} root * @param {TreeNode} p * @param {TreeNode} q * @return {TreeNode} */ var lowestCommonAncestor = function(root, p, q) { if (root.val > p.val && root.val > q.val) { return lowestCommonAncestor(root.left, p, q) } if (root.val < p.val && root.val < q.val) { return lowestCommonAncestor(root.right, p, q) } return root }; 複製代碼
前綴樹是N叉樹的一種特殊形式。前綴樹的每個節點一般表示一個字符或者字符串。每個節點擁有多個不一樣的子節點。值得注意的是,根節點表示空字符串。
前綴樹的一個重要的特性是,節點全部的後代都與該節點相關的字符串有着共同的前綴。這就是前綴樹名稱的由來。
方法1, 使用長度爲26的數組存儲子節點
方法2, 使用hashMap存儲子節點
var TrieNode = function (val = null) { // 當前的值 this.val = val // 當前節點的子節點 this.children = {} // 表示當前節點是否爲一個單詞 this.isWord = false } // 添加到節點 TrieNode.prototype.add = function (val) { let child = new TrieNode(val) this.children[val] = child return this.children[val] } // 判斷是否包含子節點 TrieNode.prototype.has = function (val) { return this.children[val] ? true : false } /** * Initialize your data structure here. */ var Trie = function() { // 初始化根節點 this.root = new TrieNode('') }; /** * Inserts a word into the trie. * @param {string} word * @return {void} */ Trie.prototype.insert = function(word) { let current = this.root let words = word.split('') let i = 0 // 替換最後一個節點 while (i < words.length) { if (!current.has(words[i])) { // 若是不存在該子節點 current = current.add(words[i]) } else { // 若是存在該子節點 current = current.children[words[i]] } i += 1 } current.isWord = true }; /** * Returns if the word is in the trie. * 判斷是否存在單詞 * @param {string} word * @return {boolean} */ Trie.prototype.search = function(word) { let current = this.root let words = word.split('') let i = 0 let result = null while (i < words.length) { if (current.has(words[i])) { current = current.children[words[i]] i += 1 } else { return false } } return current.isWord }; /** * Returns if there is any word in the trie that starts with the given prefix. * 判斷是否包含單詞 * @param {string} prefix * @return {boolean} */ Trie.prototype.startsWith = function(prefix) { let current = this.root let prefixs = prefix.split('') let i = 0 while (i < prefixs.length) { if (current.has(prefixs[i])) { current = current.children[prefixs[i]] i += 1 } else { return false } } return true }; /** * Your Trie object will be instantiated and called as such: * var obj = Object.create(Trie).createNew() * obj.insert(word) * var param_2 = obj.search(word) * var param_3 = obj.startsWith(prefix) */ 複製代碼
在英語中,咱們有一個叫作 詞根(root)的概念,它能夠跟着其餘一些詞組成另外一個較長的單詞——咱們稱這個詞爲 繼承詞(successor)。例如,詞根an,跟隨着單詞 other(其餘),能夠造成新的單詞 another(另外一個)。
如今,給定一個由許多詞根組成的詞典和一個句子。你須要將句子中的全部繼承詞用詞根替換掉。若是繼承詞有許多能夠造成它的詞根,則用最短的詞根替換它。
輸入: dict(詞典) = ["cat", "bat", "rat"]
sentence(句子) = "the cattle was rattled by the battery"
輸出: "the cat was rat by the bat"
var replaceWords = function(dict, sentence) { let sentences = sentence.split(' ') let result = [] for (let i = 0; i < sentences.length; i++) { let trie = new Trie() // 句子中的每個詞造成一個前綴樹 trie.insert(sentences[i]) let min = sentences[i] for (let j = 0; j < dict.length; j++) { // 判斷是否包含詞根 if (trie.startsWith(dict[j])) { // 取最短的詞根 min = min.length < dict[j].length ? min : dict[j] } } result.push(min) } return result.join(' ') }; 複製代碼
先訪問根節點,而後以此遍歷根節點的全部子節點。若是子節點存在子節點。同根節點同樣,先遍歷自身而後遍歷它的子節點。
var preorder = function(root) { let result = [] const iteration = (root) => { if (!root) return result.push(root.val) if (root.children) { for (let i = 0; i < root.children.length; i++) { iteration(root.children[i]) } } } iteration(root) return result } 複製代碼
優先遍歷根節點的全部子節點,若是子節點存在子節點,則優先遍歷其它的子節點
var postorder = function(root) { let result = [] const iteration = (root) => { if (!root) return if (root.children) { for (let i = 0; i < root.children.length; i++) { iteration(root.children[i]) } } result.push(root.val) } iteration(root) return result }; 複製代碼
var levelOrder = function (root) { let reuslt = [] const iteration = (root, level) => { if (!root) return if (!reuslt[level]) { reuslt[level] = [] } reuslt[level].push(root.val) for (let i = 0; i < root.children.length; i++) { iteration(root.children[i], level + 1) } } iteration(root, 0) return reuslt }; 複製代碼
給定一個 N 叉樹,找到其最大深度。最大深度是指從根節點到最遠葉子節點的最長路徑上的節點總數。
思路很簡單, BFS整個N叉樹, 返回結果的長度便可
var maxDepth = function(root) { let reuslt = [] const iteration = (root, level) => { if (!root) return if (!reuslt[level]) { reuslt[level] = [] } reuslt[level].push(root.val) for (let i = 0; i < root.children.length; i++) { iteration(root.children[i], level + 1) } } iteration(root, 0) return reuslt.length }; 複製代碼