「算法思想」分治、動態規劃、回溯、貪心一鍋燉

觀感度:🌟🌟🌟🌟🌟前端

口味:東北一鍋出git

烹飪時間:10mingithub

本文已收錄在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. 爬樓梯

雖然動態規劃的最終版本 (降維再去維) 大都不是遞歸,但解題的過程仍是離不開遞歸的。新手可能會以爲動態規劃思想接受起來比較難,確實,動態規劃求解問題的過程不太符合人類常規的思惟方式,咱們須要切換成機器思惟。

使用動態規劃思想解題,首先要明確動態規劃的三要素。

動態規劃三要素

  • 重疊子問題
  • 最優子結構
  • 狀態轉移方程

重疊子問題

切換機器思惟,自底向上思考。

爬第 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. 分發餅乾

思路

貪心算法+雙指針求解。

  • 給一個孩子的餅乾應當儘可能小而且能知足孩子,大的留來知足胃口大的孩子
  • 由於胃口小的孩子最容易獲得知足,因此優先知足胃口小的孩子需求
  • 按照從小到大的順序使用餅乾嘗試是否可知足某個孩子
  • 當餅乾 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.電話號碼的字母組合

思路

使用回溯法進行求解,回溯是一種經過窮舉全部可能狀況來找到全部解的算法。若是一個候選解最後被發現並非可行解,回溯算法會捨棄它,並在前面的一些步驟作出一些修改,並從新嘗試找到可行解。究其本質,其實就是枚舉。

若是沒有更多的數字須要被輸入,說明當前的組合已經產生。

若是還有數字須要被輸入:

  • 遍歷下一個數字所對應的全部映射的字母
  • 將當前的字母添加到組合最後,也就是 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。

相關文章
相關標籤/搜索