「觀感度:🌟🌟🌟🌟🌟」
前端
「口味:東北一鍋出」git
「烹飪時間:10min」github
本文已收錄在
Github
github.com/Geekhyt,感謝Star。web
數據結構與算法系列專欄第四彈來襲,往期專欄連接以下:算法
初學者一聽到算法思想,就會以爲它們高深莫測,只能望而卻步。編程
但若是你看過《事實》這本書,你就不會被大腦中的慣性思惟所影響。只要咱們理解算法思想的關鍵點,多作題練習並加深理解記憶。其實算法思想就像切菜同樣簡單。數組
上一篇算法系列專欄中咱們搞明白了遞歸。其實遞歸這種編程技巧是不少算法的基礎。微信
還看過的同窗建議先移步這篇專欄你真的懂遞歸嗎?cookie
好比本文講到的這幾種算法思想,大部分都是基於遞歸思想基礎上的。數據結構
一句話理解四種算法思想
分治:分而治之,先解決子問題,再將子問題的解合併求出原問題。
貪心:一條路走到黑,選擇當下局部最優的路線,沒有後悔藥。
回溯:一條路走到黑,手握後悔藥,能夠無數次重來。(英雄聯盟艾克大招無冷卻)。
動態規劃:上帝視角,手握無數平行宇宙的歷史存檔,同時發展出無數個將來。
接下來咱們一塊兒庖丁解牛,將這幾種算法思想一鍋燉。
分治算法 Divide and Conquer
分治算法思想很大程度上是基於遞歸的,也比較適合用遞歸來實現。顧名思義,分而治之。通常分爲如下三個過程:
-
分解:將原問題分解成一系列子問題。 -
解決:遞歸求解各個子問題,若子問題足夠小,則直接求解。 -
合併:將子問題的結果合併成原問題。
比較經典的應用就是歸併排序 (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;
}
複雜度分析
-
時間複雜度:O(nlogn) -
空間複雜度:O(n)
動態規劃 Dynamic Programming
LeetCode真題
70. 爬樓梯
https://leetcode-cn.com/problems/climbing-stairs/
雖然動態規劃的最終版本 (降維再去維) 大都不是遞歸,但解題的過程仍是離開不遞歸的。新手可能會以爲動態規劃思想接受起來比較難,確實,動態規劃求解問題的過程不太符合人類常規的思惟方式,咱們須要切換成機器思惟。
使用動態規劃思想解題,首先要明確動態規劃的三要素。
動態規劃三要素
-
重疊子問題
-
最優子結構
-
狀態轉移方程
重疊子問題
切換機器思惟,自底向上思考。
爬第 n 階樓梯的方法數量,等於兩部分之和:
-
爬上 n-1 階樓梯的方法數量 -
爬上 n-2 階樓梯的方法數量
最優子結構
子問題的最優解可以推出原問題的優解。
狀態轉移方程
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];
};
複雜度分析
-
時間複雜度:O(n) -
空間複雜度:O(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;
}
複雜度分析
-
時間複雜度:O(n) -
空間複雜度:O(1)
貪心算法 Greedy
最近某音很火的貪心土味情話
喂,不是吧。今天喝了脈動啊,吃了果凍啊,可是,仍是忍不住對你心動啊。
回到算法中,貪心算法
是動態規劃
算法的一個子集,能夠更高效解決一部分更特殊的問題。實際上,用貪心算法解決問題的思路,並不總能給出最優解。由於它在每一步的決策中,選擇目前最優策略,不考慮全局是否是最優。
LeetCode真題
LeetCode 455. 分發餅乾
https://leetcode-cn.com/problems/assign-cookies/description/
思路
貪心算法+雙指針求解。
-
給一個孩子的餅乾應當儘可能小而且能知足孩子,大的留來知足胃口大的孩子 -
由於胃口小的孩子最容易獲得知足,因此優先知足胃口小的孩子需求 -
按照從小到大的順序使用餅乾嘗試是否可知足某個孩子 -
當餅乾 j >= 胃口 i 時,餅乾知足胃口,更新知足的孩子數並移動指針 i++ j++ res++
-
當餅乾 j < 胃口 i 時,餅乾不能知足胃口,須要換大的 j++
關鍵點
將需求因子 g 和 s 分別從小到大進行排序,使用貪心思想配合雙指針,每一個餅乾只嘗試一次,成功則換下一個孩子來嘗試。
複雜度分析
-
時間複雜度:O(nlogn) -
空間複雜度:O(1)
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;
};
回溯算法 Backtracking
回溯算法本質上就是枚舉,使用摸着石頭過河的查找策略,還能夠經過剪枝少走冤枉路。
LeetCode真題
LeetCode 17.電話號碼的字母組合
https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
思路
使用回溯法進行求解,回溯是一種經過窮舉全部可能狀況來找到全部解的算法。若是一個候選解最後被發現並非可行解,回溯算法會捨棄它,並在前面的一些步驟作出一些修改,並從新嘗試找到可行解。究其本質,其實就是枚舉。
若是沒有更多的數字須要被輸入,說明當前的組合已經產生。
若是還有數字須要被輸入:
-
遍歷下一個數字所對應的全部映射的字母 -
將當前的字母添加到組合最後,也就是 str + tmp[r]
關鍵點
在for循環中調用遞歸。
複雜度分析
N+M 是輸入數字的總數
-
時間複雜度:O(3^N * 4^M) -
空間複雜度:O(3^N * 4^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。
公衆號:前端食堂
掘金:童歐巴
知乎:童歐巴
這是一個終身學習的男人,他在堅持本身熱愛的事情,歡迎加入前端食堂,和這個男人一塊兒開心的變胖~
推薦閱讀:
在看和轉發是莫大鼓勵❤️
本文分享自微信公衆號 - 前端食堂(webcanteen)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。