「觀感度:🌟🌟🌟🌟🌟」前端
「口味:東北一鍋出」git
「烹飪時間:10min」github
本文已收錄在
Github
github.com/Geekhyt,感謝Star。web
數據結構與算法系列專欄第四彈來襲,往期專欄連接以下:算法
初學者一聽到算法思想,就會以爲它們高深莫測,只能望而卻步。編程
但若是你看過《事實》這本書,你就不會被大腦中的慣性思惟所影響。 只要咱們理解算法思想的關鍵點,多作題練習並加深理解記憶。其實算法思想就像切菜同樣簡單。數組
上一篇算法系列專欄中咱們搞明白了遞歸。其實遞歸這種編程技巧是不少算法的基礎。cookie
還沒看過的同窗建議先移步這篇專欄你真的懂遞歸嗎?數據結構
好比本文講到的這幾種算法思想,大部分都是基於遞歸思想基礎上的。編輯器
分治:分而治之,先解決子問題,再將子問題的解合併求出原問題。
貪心:一條路走到黑,選擇當下局部最優的路線,沒有後悔藥。
回溯:一條路走到黑,手握後悔藥,能夠無數次重來。(英雄聯盟艾克大招無冷卻)。
動態規劃:上帝視角,手握無數平行宇宙的歷史存檔,同時發展出無數個將來。
接下來咱們一塊兒庖丁解牛,將這幾種算法思想一鍋燉。
分治算法思想很大程度上是基於遞歸的,也比較適合用遞歸來實現。顧名思義,分而治之。通常分爲如下三個過程:
比較經典的應用就是歸併排序 (Merge Sort)
以及快速排序 (Quick Sort)
等。咱們來從歸併排序理解分治思想,歸併排序就是將待排序數組不斷二分爲規模更小的子問題處理,再將處理好的子問題合併起來。
上代碼。
const mergeSort = function(arr) {
const len = arr.length; if (len > 1) { // 對半分解 const middle = Math.floor(len / 2); const left = arr.slice(0, middle); const right = arr.slice(middle, len); let i = 0; let j = 0; let k = 0; // 分別對左右進行排序 mergeSort(left); mergeSort(right); while(i < left.length && j < right.length) { if (left[i] < right[j]) { arr[k] = left[i]; i++; } else { arr[k] = right[j]; j++; } k++; } // 檢查餘項 while(i < left.length) { arr[k] = left[i]; i++; k++; } while(j < right.length) { arr[k] = right[j]; j++; k++; } } return arr; } 複製代碼
雖然動態規劃的最終版本 (降維再去維) 大都不是遞歸,但解題的過程仍是離不開遞歸的。新手可能會以爲動態規劃思想接受起來比較難,確實,動態規劃求解問題的過程不太符合人類常規的思惟方式,咱們須要切換成機器思惟。
使用動態規劃思想解題,首先要明確動態規劃的三要素。
重疊子問題
最優子結構
狀態轉移方程
切換機器思惟,自底向上思考。
爬第 n 階樓梯的方法數量,等於兩部分之和:
子問題的最優解可以推出原問題的優解。
dp[n] = dp[n-1] + dp[n-2]
具有三要素,確認邊界條件,初始化狀態,開始切菜:
dp[0] = 1
dp[1] = 1
const climbStairs = function(n) {
const dp = []; dp[0] = 1; dp[1] = 1; for (let i = 2; i <= n; i++) { dp[i] = dp[i-1] + dp[i-2]; } return dp[n]; }; 複製代碼
在此基礎上,咱們還能夠經過壓縮空間來對算法進行優化。由於 dp[i]
只與 dp[i-1]
和 dp[i-2]
有關,沒有必要存儲全部出現過的 dp 項,只用兩個臨時變量去存儲這兩個狀態便可。
const climbStairs = function(n) {
let a1 = 1; let a2 = 1; for (let i = 2; i <= n; i++) { [a1, a2] = [a2, a1 + a2]; } return a2; } 複製代碼
最近某音很火的貪心土味情話
喂,不是吧。今天喝了脈動啊,吃了果凍啊,可是,仍是忍不住對你心動啊。
回到算法中,貪心算法
是動態規劃
算法的一個子集,能夠更高效解決一部分更特殊的問題。實際上,用貪心算法解決問題的思路,並不總能給出最優解。由於它在每一步的決策中,選擇目前最優策略,不考慮全局是否是最優。
貪心算法+雙指針求解。
i++ j++ res++
j++
將需求因子 g 和 s 分別從小到大進行排序,使用貪心思想配合雙指針,每一個餅乾只嘗試一次,成功則換下一個孩子來嘗試。
const findContentChildren = function (g, s) {
g = g.sort((a, b) => a - b); s = s.sort((a, b) => a - b); let gi = 0; // 胃口值 let sj = 0; // 餅乾尺寸 let res = 0; while (gi < g.length && sj < s.length) { if (s[sj] >= g[gi]) { gi++; sj++; res++; } else { sj++; } } return res; }; 複製代碼
回溯算法本質上就是枚舉,使用摸着石頭過河的查找策略,還能夠經過剪枝少走冤枉路。
使用回溯法進行求解,回溯是一種經過窮舉全部可能狀況來找到全部解的算法。若是一個候選解最後被發現並非可行解,回溯算法會捨棄它,並在前面的一些步驟作出一些修改,並從新嘗試找到可行解。究其本質,其實就是枚舉。
若是沒有更多的數字須要被輸入,說明當前的組合已經產生。
若是還有數字須要被輸入:
str + tmp[r]
在for循環中調用遞歸。
N+M 是輸入數字的總數
const letterCombinations = function (digits) {
if (!digits) { return []; } const len = digits.length; const map = new Map(); map.set('2', 'abc'); map.set('3', 'def'); map.set('4', 'ghi'); map.set('5', 'jkl'); map.set('6', 'mno'); map.set('7', 'pqrs'); map.set('8', 'tuv'); map.set('9', 'wxyz'); const result = []; function generate(i, str) { if (i == len) { result.push(str); return; } const tmp = map.get(digits[i]); for (let r = 0; r < tmp.length; r++) { generate(i + 1, str + tmp[r]); } } generate(0, ''); return result; }; 複製代碼
1.看到這裏了就點個贊支持下吧,你的「贊」是我創做的動力。
2.關注公衆號前端食堂,「你的前端食堂,記得按時吃飯」!
3.本文已收錄在前端食堂Github
github.com/Geekhyt,求個小星星,感謝Star。