前言
前端學演算法還是有必要性的,不說現在大廠面試都考演算法,就是做許可權模組的時候,如果不懂點演算法,寫樹結構的自定義遍歷都寫不出來。
今天把最近刷題的遇到的動態規劃和dfs做一個總結,希望對大家學習演算法能有所幫助。
DP和dfs關係
動態規劃(dp):
- 是從下往上的一個遍歷。
- 需要一個遞推公式比如f(n)=f(n-1)+f(n-2)這種。
- 不需要遞迴只需要普通的迭代,所以效能好。
深度優先搜尋(dfs):
- 是從上往下的一個遞迴。
- 也需要一個遞推公式比如f(n)=f(n-1)+f(n-2)這種。
- 從上往下遞迴遇到終止條件後又會不斷的從下往上取值(遞迴本質),過程類似dp
- 由於遍歷所有可能性,所以效能特別差,一般會剪枝
- 一般剪枝手法就是按條件提前終止或者記憶化
聯絡
- 記憶化後的dfs其實本質上和dp差不多
- 因為兩者都要遞推公式,所以理論上dfs能解的題dp都能解
- 不過有的題適合dfs,比如
求解可能性
的題目
一道經典題目雙解
leetcode322. 兌換零錢
dp
var coinChange = function(coins, amount) {
/**
遍歷i
dp(n) = min( dp(n-conin[i]) + 1)
*/
coins.sort((a,b)=>b-a)
const arr = new Array(amount+1).fill(0)
for(let i =1;i<=amount;i++){
let curAmount = i //總額
for(let j=0;j<coins.length;j++){
let curConin = coins[j] //當前兌換硬幣
if(curConin<=curAmount){
let res = arr[curAmount-curConin]+1 //這個數必然大於等於0
if(res===0) continue //等於0則這種找零不可取
arr[i] = arr[i]===0?res:Math.min(arr[i],res) //等於0還沒有初始化,先初始化一下
}
}
if(!arr[i]) arr[i]=-1
}
// console.log(arr)
return arr[amount]
};
dfs
不剪枝會超時,用快取去剪枝,剪枝了一般效率還是打不過Dp,除非有非常好的剪枝策略
var coinChange = function(coins, amount) {
coins.sort((a,b)=>b-a)
let cache = {}
const dfs = (restamount)=>{
if(restamount===0) return 0
if(restamount<coins[coins.length-1]) return -1 //剩餘比最小面額還小則 無法成功兌換
if(cache[restamount])return cache[restamount]
if(restamount<coins[coins.length-1]) return
for(var i=0;i<coins.length;i++){
if(coins[i]<=restamount){
let res = dfs(restamount-coins[i])+1
if(res===0) continue
cache[restamount] = cache[restamount]?Math.min(cache[restamount],res):res
}
}
// 當走到這裡的時候cache[restamount]實際已經確定了 自頂向下深度優先遍歷實際預設有一個自底向上的過程,所以天生比dp慢
if(!cache[restamount]) cache[restamount]=-1
return cache[restamount]
}
return dfs(amount)
};
適合dsf的題目
leetcode46. 全排列
求解可能性的題目,非常適合用dfs.
如果用dp,程式碼會很難組織
var permute = function(nums) {
//dfs
/**
dp(n) = dp(n-1)陣列中每個元素在i位置(0<=i<=n)新增n
*/
const dfs=(nums)=>{ // [1,2]
if(nums.length===1) return [nums.slice(0,1)]
const arr = dfs(nums.slice(0,-1))
const res = []
arr.forEach(item=>{
for(var i=0;i<=item.length;i++){
let addedItem = item.slice()
addedItem.splice(i,0,nums[nums.length-1])
res.push(addedItem)
}
})
return res
}
return dfs(nums)
};
適合dp的題目
leetcode198. 打家劫舍
這個題目用dp解就很清爽,當然遞推公式包含貪心思想。
var rob = function(nums) {
//dp(n) = max(dp(n-2)+nums[n] , dp(n-1)) 總共有N家,貪心。從左往右輪到第N家的時候的收益
const dp = new Array(nums.length).fill(0)
dp[1] = nums[0]
for(let i=2;i<=nums.length;i++){
dp[i] = Math.max(dp[i-2]+nums[i-1] , dp[i-1]) //輪到第i家的收益
}
return dp[nums.length]
};