前端總結資料結構與演算法基礎
阿新 • • 發佈:2020-06-28
翻書問題或者走臺階問題。
問:共有n個臺階,每次只能上1個臺階或者2個臺階,共有多少種方法爬完臺階?共有n頁書,每次只能翻1頁或者2頁書,共有多少種方法翻完全書?
ps:本質上是斐波那契數列問題。假設只有一個臺階,則只有一種跳法,f(1)=1;如果兩個臺階,那麼有兩種跳法:1,一次跳一級,2,一次跳兩級,f(2) = 2。如果大於2的n級臺階,那麼一次跳一級臺階,剩下還有n-1級臺階,有f(n-1)種跳法。假如一次跳2級臺階,剩下n-2級臺階,有f(n-2)中跳法。這就表示f(n) = f(n-1)+f(n-2)。
function fibonacci(n) { if (n === 1 || n === 2) {return n; } else { return fibonacci(n-1) + fibonacci(n-2) } } // 一個記憶化的斐波那契數列 let tem = [0, 1] function fibonacci(n) { let result = tem[n] if(typeof result !== 'number') { result = fibonacci(n-1)+fibonacci(n-2) tem[n] = result // 將每次 fibonacci(n) 的值都快取下來 } returnresult } // 動態規劃:從底部開始解決問題, 將所有小問題解決掉, 然後合併成一個整體解決方案, 從而解決掉整個大問題 。 function fibonacci(n) { let last = 1; let nextLast = 1; let result = 1; for (let i = 2; i < n; ++i) { result = last + nextLast; nextLast = last; last = result; } return result; }
二分查詢。
陣列array包含了順序的元素,[1,2,3,...,10],查詢目標元素t是否在陣列中。
我們已經提前知道陣列是順序排列的,比如遞增順序。
時間複雜度為O(logN)
遞推公式:
f(N) = f(N/2) + O(1) = f(N/4) + 2 * O(1)
假設 N = 2 ^ M
最後可以推出
f(N) = O(logN)
let list = [1,2,3,4,5,6,7,8,9,10] function binarySearch(array, t) { let left = 0, right = array.length - 1; while(left <= right) { let mid = parseInt((right + left)/2) if(array[mid] < t) { left = mid + 1 } else if(array[mid] > t) { right = mid - 1 } else { return true } } return false } // 遞迴寫法 function binarySearch2(array, t, left, right) { if(left > right) return -1 let mid = parseInt((right - left)/2) + left if(array[mid] === t) { return mid }else if(array[mid] < t){ return binarySearch2(array, t, mid + 1, right) }else if(array[mid] > t){ return binarySearch2(array, t, left, mid - 1) } } let test1 = binarySearch(list, 5) let test2 = binarySearch2(list, 5, 0, list.length-1) console.log(test1, test2)
貪心演算法--最少硬幣找零問題
所謂貪心,就是先選擇當前階段的最優解,不去考慮這次的選擇會不會對未來造成影響,想要通過每個階段的區域性最優解,達到全域性最優解。
假設你為一家自動售貨機廠家程式設計序,自動售貨機要每次找給顧客最少數量硬幣,美國有以下面額的硬幣:1美分、5美分、10美分、25美分。比如說要找36美分的零錢,我們可以用1個25美分、1個10美分和一個1美分。
(ps:找零問題,先找大額的硬幣25分,依次遞減)
function minCoinChange(amount, coins) { let minCoin = amount let count = 0 for(let i = 0, l = coins.length; i < l; i++) { if (coins[i] <= minCoin) { count += Math.floor(minCoin / coins[i]) console.log(coins[i] + '*' + Math.floor(minCoin / coins[i])) minCoin = minCoin % coins[i] } } return count } let counts = minCoinChange(61, [25, 10, 5, 1]) console.log(counts)
動態規劃--01揹包問題
function main(volume, value, c){ let tArray = [] let useGoodsNo = [] for(let i = 0, l = volume.length; i <= l; i++) { tArray[i] = [] useGoodsNo[i] = 0 for(let j = 0; j <= c; j++) { if(i == 0 || j == 0){ tArray[i][j] = 0 } } } volume.unshift(0) //讓i = 1的時候對應的是1號物品 value.unshift(0) for(let i = 1, l = volume.length; i < l; i++) { // i從1開始 tArray[0][j]已經填滿 for(let j = 1; j <= c; j++) { if(j < volume[i]){ tArray[i][j] = tArray[i-1][j] } else { //如果裝了剩下的體積 let leftVolume = j - volume[i] // 當前物品的價值 let curValue = value[i] // 剩下的體積的價值是 (tArray[i - 1]因為0 1揹包,不能重複放同個物品) let leftValue = tArray[i - 1][leftVolume] tArray[i][j] = Math.max(tArray[i-1][j], curValue+leftValue) } } } // 填滿表格 console.table(tArray) // 逆向獲取物品 let C = c for(let i = value.length-1; i > 0; i--){ if(tArray[i][C] > tArray[i-1][C]){ useGoodsNo[i] = 1 C = C - volume[i] } } console.table(useGoodsNo) // 所選的物品 console.log(tArray[value.length-1][c]) // 最大價值 } main([2,3,4,5], [3,4,5,6], 8)
展平陣列
// 展平陣列 [[1, 2], 3, [[[4], 5]]] => [1, 2, 3, 4, 5] function flatten(arr) { return [].concat( ...arr.map(x => Array.isArray(x) ? flatten(x) : x) ) }
隨機打亂陣列
function shuffle(arr) { for(let i = 0; i < arr.length-1; i++){ const j = i + Math.floor(Math.random() * (arr.length - i)) [arr[i], arr[j]] = [arr[j], arr[i]] } }
函式節流
// 函式節流 function throttle(func, delay = 60) { let lock = false return (...args) => { if(lock) return func(...args) lock = true setTimeout(()=> { lock = false }, delay) } }
柯里化
//對於curry(foo),g函式引數足夠4個,就呼叫foo(a,b,c,d),如果小於4就返回一個可以繼續積累引數的函式 const curry = func => { const g = (...allArgs) => allArgs.length >= func.length ? func(...allArgs) : (...args) => g(...allArgs, ...args) return g } const foo = curry((a, b, c, d) =>{ console.log(a, b, c, d) }) foo(1)(2)(3)(4) // 1 2 3 4 foo(1)(2)(3) // 不返回 const f = foo(1)(2)(3) f(4) // 1 2 3 4 5
Y組合子
const y = function(le) { return function (f) { return f(f) }(function (f) { return le( function(...x) { return (f(f))(...x) } ) }) } const curryY = func => y(g => { (...allArgs) => { allArgs.length >= func.length ? func(...allArgs) : (...args) =>g(...allArgs, ...args) } } ) const foo = curryY((a, b, c, d) => { console.log(a, b, c, d) }) foo(1)(2)(3)(4)
連結串列
連結串列中最簡單的一種是單向連結串列,它包含兩個域,一個資訊域和一個指標域。這個連結指向列表中的下一個節點,而最後一個節點則指向一個空值。
function ListNode(val) { this.val = val this.next = null } let node1 = new ListNode(1) let node2 = new ListNode(2) let node3 = new ListNode(3) node1.next = node2 node2.next = node3 console.log(node1) function recursiveTraverse(head) { if(head != null) { console.log(head.val) recursiveTraverse(head.next) } } recursiveTraverse(node1) // 翻轉一條單向連結串列 // 輸入 1 -> 2 -> 3 -> null // 輸出 3 -> 2 -> 1 -> null function reverseLinkedList(head){ let dummy = head let tem = dummy while(head != null && head.next != null){ dummy = head.next head.next = dummy.next dummy.next = tem tem = dummy } return dummy } let reverseLink = reverseLinkedList(node1) console.log(reverseLink)
二叉樹
function TreeNode(val) { this.val = val this.left = null this.right = null } let root = new TreeNode(2) root.left = new TreeNode(1) root.right = new TreeNode(3) console.log(root) /* * 2 * / \ * 1 3 */ /* 中序遍歷:( 定義: 1,中序遍歷左子樹 2,遍歷根節點 3,中序遍歷右子樹 ) 前序遍歷:( 定義: 1,遍歷根節點 2,前序遍歷左子樹 3,前序遍歷右子樹 ) 後序遍歷:( 定義: 1,後序遍歷左子樹 2,後序遍歷右子樹 3,遍歷根節點 ) */ function inOrder(root) { if(root) { console.log('前',root.val) // 2 1 3 inOrder(root.left) console.log('中',root.val) // 1 2 3 inOrder(root.right) console.log('後',root.val) // 1 3 2 } } inOrder(root) /* 二叉查詢樹/二叉搜尋樹:( 定義: 左子樹的所有節點的值小於根節點 右子樹的所有節點的值大於根節點 左子樹和右子樹都是二叉查詢樹 ) 二叉搜尋樹的中序遍歷就是排好序的元素。 */ function binarySearchTreeFind(root, target) { if(!root) return false if(root.val === target) { return true } else if(root.val < target){ return binarySearchTreeFind(root.right, target) } else if(root.val > target) { return binarySearchTreeFind(root.left, target) } } console.log(binarySearchTreeFind(root, 1))
棧
First In Last Out(FILO)
先進後出,後進先出
function Stack(){ this.stack = [] this.isEmpth = function(){ return this.size() === 0 } this.size = function() { return this.stack.length } // 出棧 this.pop = function(){ if(this.isEmpth()){ return null } else { return this.stack.pop() } } // 進棧 this.push = function (val){ return this.stack.push(val) } // 返回棧頂元素 this.peak= function(){ if(this.isEmpth()) { return null } else { return this.stack[this.stack.length - 1] } } } let test = new Stack() test.push(1) console.log(test.peak()) console.log(test.isEmpth()) test.pop() console.log(test.peak())
佇列
First In First Out(FIFO)
先進先出
function Queue() { this.queue = [] this.size = function() { return this.queue.length } this.isEmpty = function(){ return this.size() === 0 } // 入佇列 this.enqueue = function (val){ this.queue.unshift(val) } // 出佇列 this.dequeue = function(){ if(this.isEmpty()){ return null } else { return this.queue.pop() } } } let test = new Queue() test.enqueue(1) test.enqueue(2) console.log(test.queue) test.dequeue() console.log(test.queue)