符合人類思惟的動態規劃

焦慮嘮嗑

首先聲明一下,我沒有賣焦慮,是我本身焦慮了。其次,要感謝個人師傅,西湖區最帥.....前端

好了,言歸正傳,在leetCode評論區你均可以看到 lucifer,簡稱路西法大佬;他的題解纔是符合人類思惟的思考方式,思考的點都是很深刻淺出的。git

爲何要刷算法呢?由於如今大前端時代,而前端開發確實是比其它領域要稍微簡單一點的,注意,我並無說前端領域簡單,是入門相對簡單。入門簡單,就意味着入門的人會很是的多,那怎麼在衆多的初級前端中脫穎而出呢?我選擇了刷算法,刷算法有如下幾點好處。算法

  1. 在遇到須要算法的業務需求時候,能夠徹底不虛。
  2. 能夠鍛鍊本身的邏輯思惟,鍛鍊內功。
  3. 能夠更快的學習一些新技術。

到底什麼是動態規劃

你說的我都會,我也能看得懂,爲何一說/看就會,一寫就費呢?數組

由於大多數題解,甚至是leetcode評論區的題解,只會告訴答案,不會告訴你思考答案的方式。或者有些會告訴你怎麼思考,可是他們都是經驗豐富的dp選手,思考方式徹底不適合新手,一個動態轉移方程莫名其妙的就出來了???滿臉的黑人問號,我曾經也是這樣走過來的,再次感謝路西法大佬的指點。markdown

到底咱們要以什麼方式來學習動態規劃呢?個人建議是硬着頭皮先刷點簡單的,層層遞進,再刷點困難的;刷題的過程當中千萬不能浮躁,不要爲了AC而AC,而是要經過本身耐心的觀察,抽象,練習,概括總結;遞歸直至理解dp,熟悉dp。真的沒有什麼快捷的方式,若是有,都是騙人的。這裏,結合實戰來帶你們過一遍,用符合人類的思考方式徹底扒開動態規劃的褲子,學習(gandiao)動態規劃。ide

若是你不熟悉動態規劃,或者徹底不瞭解,我建議你先看看我上一篇 動態規劃 的文章。oop

53.最大子序和

好了,咱們直接上菜,先來看看 題目 ;post

            

什麼樣的題目適合用動態規劃?

不要多想,咱們先以符合人類最簡單的思惟方式暴力求解,再根據狀態樹考慮如下兩點。學習

那這題怎麼暴力呢?直接枚舉nums,以nums[i]爲起點,不斷的加到最後一位,加的過程當中維護一個最大值便可,我寫下代碼。千萬不要看不起暴力求解,是dp的突破口!ui


咱們來看下這個暴力的狀態樹,我只畫出前面最長的兩個分支,其它自行腦補。

       

分析一下這個暴力狀態樹兩個分支,很明顯答案都是是 [4,-1,2,1] ,後一個分支比前一個分支少一個-2,也就是問題的規模變小了,答案依然是最優的,這就存在最優子結構

咱們在算第二個分支的時候,其實前面第一個分支已經算過了,這就是重複子問題

通常這種最值型,都比較適合dp來求解;如何能快速的用dp求解,就靠你本身去攢經驗了。

定義狀態是動態規劃的定海神針

狀態定義的對不對,直接是決定了你的dp方程式對不對,從而決定了你dp的方式對不對;

【定義狀態】以前,咱們先要搞清楚兩個東西,一個是【狀態】,一個是【選擇】

  • 狀態:通常直接是題目給定的條件,本題給定的條件就是nums,那每一個狀態就是nums[i]
  • 選擇:對於每一個狀態,有幾種選擇?本題的nums[i]就有兩種狀況,要麼選擇nums[i]作爲結果,要麼就不選擇,若是不選擇,由於要求連續,就是另起爐竈嘛。

注意:這裏的【狀態】【定義狀態】是兩個東西,【狀態】是題目給出的條件,會影響結果的條件,而定義【定義狀態】是爲了明確dp方程式的含義,以便後面根據狀態的變化,利用數學概括法得出狀態轉移方程,說白了就是找規律。

舉個例子,x + y = z,你必需要明確你的x,y,z是啥,你才能寫出這樣的狀態轉移方程;

到這裏,咱們明確了【狀態】【選擇】,而定義好轉移方程的狀態;咱們必需要有如下兩個意識。

  1. 化成子問題(問題規模變小)去想,去思考。對應這題,咱們不妨把數組逐漸變小了想。
  2. 最後一步是什麼,也就是最後一個解;(最優策略中使用的最後一個選擇)

以題目爲例,nums = [-2,1,-3,4,-1,2,1,-5,4],最後一步是什麼?最優策略中使用的最後一個選擇是什麼?很明顯這道題的最後一個選擇是nums[6] = 1,若是【不選擇】1,上面咱們說過了,就是另起爐竈;若是選擇1,答案就是最終的 [4,-1,2,1];

根據以上的兩點,結合明確的兩個東西,一個是【狀態】,另外一個【選擇】

  1. 狀態:很明顯就是nums[i],每一個數就是一個狀態
  2. 狀態選擇:對於每一個狀態nums[i],咱們有兩個選擇,要麼是選擇nums[i],要麼是不選擇

到這裏,狀態的定義咱們就能夠很明確的得出來了。

dp[i] = x,表示以 nums[i] 結尾的最大子序和爲x; i < nums.length

狀態轉移方程

必定要想清楚狀態的定義,狀態的定義直接是決定了你的轉移方程對不對,dp的姿式對不對;

根據上面狀態的定義分析,咱們來找下規律(數學概括法);nums[i] 是一個個的狀態;對於每一個狀態,咱們能夠選擇,或者不選擇,若是選擇以nums[i]爲結果,那答案就是 nums[i] + dp[i-1],由於要連續,全部得加上前面的;若是不選擇,就是另起爐竈,以nums[i]開頭;枚舉全部的狀態,取兩種選擇的最大值,不就是答案了嗎?這就是狀態轉移方程了;

dp[i] = max(nums[i],dp[i - 1] + nums[i]),i > 1

初始條件和邊界

由於咱們枚舉全部的狀態,取兩種選擇的最大值就是答案,因此邊界就是數組長度;初始值是什麼呢?很明顯就是數組自己,即dp[i] = nums[i];

代碼

必定要明確上面的狀態定義,轉移方程,邊界和初始值纔開始寫代碼,有一點不明白都不能寫代碼,否則基本一寫就費。想清楚了寫代碼也要很是細心。


152.乘積最大子數組

咱們用一樣的套路解決乘積最大子數組


暴力求解就不解釋了,同上;

先來明確 【狀態】 和 【選擇】,這題一樣的,狀態就是一個個 nums[i],而對於每一個 nums[i] 狀態,一樣用是選或不選兩個選擇;可是這題有一個比較隱晦的條件,須要考慮進去,就是兩個數相乘:

  • 若是是最大值(假設正數)乘以一個負數,就是最小值
  • 若是最小值(假設負數)乘以一個負數,就是最大值

那咱們在枚舉全部的狀態時候,根據這兩個條件,不斷的維護最大最小值,遇到負數就乘以最小值,遇到正數就是乘以最大值便可。結果只和最大最小值有關係,那咱們枚舉狀態不斷更新這兩個值就能夠求出答案了,其實若是是遇到0,狀況也是同樣的;

狀態的定義:

  • imin = min(min(nums[i] * imax, nums[i] * imin), nums[i]) 
  • imax = max(max(nums[i] * imax, nums[i] * temp), nums[i])
tips:這裏要注意,temp = 上次的 imin。

初始條件就是第一個數nums[i]

這裏直接給出代碼了。QAQ


總結

我覺的dp是最能體現代碼功底的,爲何呢?由於它難。

必定要多練習,看懂了只是我懂了,你要真懂必須多練;

另外推薦幾個我以爲寫得還不錯的文章和視頻,沒有打廣告QAQ

九章dp視頻

一個還不錯的文章

一個很強的B站博主

相關文章
相關標籤/搜索