打家劫舍問題:動態規劃的解題步驟

2021-02-23 LOA算法學習筆記

動態規劃就是把一個大問題一步步降解成越來越小的子問題,直到子問題小到可以用確定的條件來解答,即按照順序,從基元問題一步步擴大問題的規模,直到問題的規模覆蓋了我要求解的問題。每一個規模的問題的解叫做一個狀態,每個不同規模的問題的解的關係叫做狀態轉移方程,實際應用中無非就是利用歷史記錄,來避免我們的重複計算。而這些歷史記錄,我們得需要一些變量來保存,一般是用一維數組或者二維數組來保存,綜上所述的動態規劃問題其實可以簡化成為三個步驟,用於解決大部分的動態規劃問題。

本期例題:LeetCode 198. House Robber 打家劫舍(Easy)

你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。

給定一個代表每個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。

示例:

輸入: [1,2,3,1]

輸出: 4

解釋: 偷竊 1 號房屋 (金額 = 1) ,然後偷竊 3 號房屋 (金額 = 3)。

偷竊到的最高金額 = 1 + 3 = 4 。

定義子問題

寫出子問題的遞推關係

確定 DP 數組的計算順序

步驟一:定義子問題

子問題是和原問題相似,但規模較小的問題。例如這道打家劫舍問題,原問題是「從全部房子中能偷到的最大金額」,將問題的規模縮小,子問題就是「從  k個房子中能偷到的最大金額」,用  f(k)表示。

圖1 打家劫舍問題的子問題定義

可以看到,子問題是參數化的,我們定義的子問題中有參數k 。假設一共有  n個房子的話,就一共有  n個子問題。動態規劃實際上就是通過求這一堆子問題的解,來求出原問題的解。這要求子問題需要具備兩個性質:

這一步是求解動態規劃問題最關鍵的一步。然而,這一步也是最無法在代碼中體現出來的一步,我們來分析一下這道題的遞推關係:

假設一共有  n個房子,每個房子的金額分別是 

  k個房子中最後一個房子是Hk-1 。如果不偷這個房子,那麼問題就變成在前  k-1個房子中偷到最大的金額,也就是子問題 f(k-1)。如果偷這個房子,那麼前一個房子  Hk-2顯然不能偷,其他房子不受影響。那麼問題就變成在前  k-1個房子中偷到的最大的金額。兩種情況中,選擇金額較大的一種結果。

另外,我們在寫遞推關係的時候,還要注意遞推關係的 base case。這樣才能構成完整的遞推關係,後面寫代碼也不容易在邊界條件上出錯。在這道題中,是k=0  和  k=1時的子問題:

在確定了子問題的遞推關係之後,下一步就是依次計算出這些子問題了。

DP 數組也可以叫「子問題數組」,因為 DP 數組中的每一個元素都對應一個子問題。如下圖所示,dp[k] 對應子問題 f(k),即偷前 k 間房子的最大金額。

圖3 DP 數組與子問題的對應關係

那麼,只要搞清楚了子問題的計算順序,就可以確定 DP 數組的計算順序。對於打家劫舍問題,我們分析子問題的依賴關係,發現每個  f(k)依賴f(k-1)  和 f(k-2)。也就是說,dp[k] 依賴 dp[k-1] 和 dp[k-2],如下圖所示。

圖4 DP 數組的依賴順序

那麼,既然 DP 數組中的依賴關係都是向右指的,DP 數組的計算順序就是從左向右。這樣我們可以保證,計算一個子問題的時候,它所依賴的那些子問題已經計算出來了。

確定了 DP 數組的計算順序之後,我們就可以寫出題解代碼了:

public int rob(int[] nums) {     if (nums.length == 0) {         return 0;     }          

int N = nums.length; int[] dp = new int[N+1]; dp[0] = 0; dp[1] = nums[0]; for (int k = 2; k <= N; k++) { dp[k] = Math.max(dp[k-1], nums[k-1] + dp[k-2]); } return dp[N]; }

    本文用打家劫舍問題展示了動態規劃問題的基本解題三步驟。動態規劃問題種類繁多,有些題目非常有難度,但這些問題千變萬化也離不開這三個基本解題步驟,只不過是把三步驟的某些步驟變得更難了。例如:

在「定義子問題」的步驟,有的題目需要定義二維、三維的子問題。

在「子問題遞推關係」的步驟,有的題目需要分情況討論,有複雜的 max、min、sum 表達式。

在「DP 數組計算順序」的步驟,有的題目需要反向計算,甚至斜向計算。

相關焦點

  • LeetCode 例題精講 | 14 打家劫舍問題:動態規劃的解題四步驟
    本文選擇一個非常典型的動態規劃問題:打家劫舍,在一步步求解這道題的過程中,講解動態規劃題目的四個基本步驟。動態規劃的解題四步驟 動態規劃的的四個解題步驟是:不瞞你說,在 LeetCode 上我做過的幾十道動態規劃題目,都是用這個解題四步驟做出來的。這個解題步驟適用於任何一道動態規劃題目,能夠讓你很快理清解題的各個要點。
  • 經典動態規劃:打家劫舍系列問題
    有好幾位讀者私下問我 LeetCode 「打家劫舍」系列問題(英文版叫 House Robber)怎麼做,我發現這一系列題目的點讚非常之高,是比較有代表性和技巧性的動態規劃題目,今天就來聊聊這道題目。打家劫舍系列總共有三道,難度設計非常合理,層層遞進。
  • LeetCode 例題精講 | 15 最長公共子序列:二維動態規劃的解法
    在上一篇文章中,我們以打家劫舍(House Robber)問題為例講解了動態規劃問題的一般解題步驟。不過,打家劫舍問題是一維動態規劃問題,而還有很多題目屬於二維動態規劃。今天我們就以一道經典的「最長公共子序列」問題講解二維動態規劃的解法。
  • 關於動態規劃,你該了解這些!
    什麼是動態規划動態規劃,英文:Dynamic Programming,簡稱DP,如果某一問題有很多重疊子問題,使用動態規劃是最有效的。上述提到的背包問題,後序會詳細講解。動態規劃的解題步驟做動規題目的時候,很多同學會陷入一個誤區,就是以為把狀態轉移公式背下來,照葫蘆畫瓢改改,就開始寫代碼,甚至把題目AC之後,都不太清楚dp[i]表示的是什麼。
  • LeetCode 例題精講 | 16 最大子數組和:子數組類問題的動態規劃技巧
    Maximum Length of Repeated Subarray 最長公共子數組(Medium)在前面的文章中,我們分別講解了一維和二維動態規劃問題的解題步驟與基本思路。不過,僅僅掌握基本步驟是不夠的。要想熟練做出動態規劃題目,還要掌握足夠的解題技巧。
  • 動態規劃高頻題匯總 | 今日直播劃重點
    動態規劃的解題要領動態規劃三大類常見動態規劃類型總結美西 1月15日 周二 19:00-21:00 p.m北京 1月16日 周三 11:00-13:00 a.m想要了解更多動態規劃的解題套路,可以報名今日《動態規劃專題班》免費試聽。
  • 總結 | 動態規劃十問十答
    今天給大家總結動態規劃十問十答,快速幫你掃盲動態規劃。答:動態規劃是一種通過「大而化小」的思路解決問題的算法。區別於一些固定形式的算法,如二分法,寬度優先搜索法,動態規劃沒有實際的步驟來規定第一步做什麼第二步做什麼。所以更加確切的說,動態規劃是一種解決問題的思想。
  • 動態規划算法秘籍
    動態規劃也是把原問題分解為若干子問題,然後自底向上,先求解最小的子問題,把結果存儲在表格中,在求解大的子問題時,直接從表格中查詢小的子問題的解,避免重複計算,從而提高算法效率。4.1.2 算法要素什麼問題可以使用動態規劃呢?
  • 乾貨總結 | 動態規劃十問十答
    今天給大家總結動態規劃十問十答,快速幫你掃盲動態規劃。答:動態規劃是一種通過「大而化小」的思路解決問題的算法。區別於一些固定形式的算法,如二分法,寬度優先搜索法,動態規劃沒有實際的步驟來規定第一步做什麼第二步做什麼。所以更加確切的說,動態規劃是一種解決問題的思想。
  • 初三月考題精選(5):幾何動態問題的解題思路分析
    解析:解決幾何動態問題,最好的思路分析及解題方法是:解題思路的延續性(1)思路:連接CF,EF、CF分別直角三角形DEB、CDB斜邊上的中線,故BD=2EF=2CF,所以CF=EF,由外角性質∠1=∠2+∠3及∠2=∠3可得∠1=2∠3,同理可得∠4=2∠6,則∠EFC=∠1+∠4=2∠3+2∠6=2(∠3+∠6)=90°,即△EFC是一個等腰直角三角形,所以CE=√2
  • 「算法與數據結構」一張腦圖帶你看動態划算法之美
    聯繫👉TianTianUp,遇到問題的話,可以聯繫作者噢,願意陪你一起學習一起探討問題。腦圖👇什麼是動態規划動態規劃(Dynamic Programming),因此常用 DP 指代動態規劃。動態規劃,首先我們得清楚,它的概念是啥,它能解決什麼問題,維基百科對它的解釋👇❝動態規劃在尋找有很多重疊子問題的情況的最佳解時有效。它將問題重新組合成子問題,為了避免多次解決這些子問題,它們的結果都逐漸被計算並被儲存,從簡單的問題直到整個問題都被解決。因此,動態規劃儲存遞歸時的結果,因而不會在解決同樣的問題時花費時間。動態規劃只能應用於有最佳子結構的問題。
  • 動態規劃初級試煉場
    動態規劃初級試煉場友情提示:本文中列舉的部分例題,並不一定只能用動態規劃求解,但文中只會給出問題的動態規劃解法。這篇文章首先會介紹一下下動態規劃的一般解題思路,然後會按照這個思路講解四個 動態規劃解題一般分為三步:每一種狀態都是使用數組的維數來表示的。一般我們會先使用數組的第一維來表示階段,然後再根據需要通過增加數組維數,來添加其它狀態。狀態轉移指的是,當前階段的狀態如何通過之前計算過的階段狀態得到。初始化邊角狀態,以及第一個階段的狀態。
  • 計算機解決問題沒有奇技淫巧,但動態規劃還是有點套路
    當然,見的多了,思考多了,是可以一步寫出非遞歸的動態規劃解法的。任何技巧都需要練習,我們先遵循這個流程走,算法設計也就這些套路,除此之外,真的沒啥高深的。以下,先通過兩個個比較簡單的例子:斐波那契和湊零錢問題,揭開動態規劃的神秘面紗,描述上述三個流程。後續還會寫幾篇文章探討如何使用動態規劃技巧解決比較複雜的經典問題。
  • Leetcode刷題-動態規劃
    本文對leetcode上部分動態規劃問題進行分析並用python實現5. 最長回文子串 題目描述給定一個字符串 s,找到 s 中最長的回文子串。你可以假設 s 的最大長度為 1000。思路2:動態規劃打家劫舍 題目描述:你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
  • 動態規劃 - 矩陣鏈相乘
    一、動態規劃的簡單介紹1. 動態規劃的定義:動態規劃,通過把原問題分解為相對簡單的子問題的方式求解複雜問題的方法。2.動態規劃的特徵:重疊子問題、最優子結構、無後效性最優子結構是指一個問題的最優解如果包含其子問題的最優解,則這個問題具有最優子結構。無後效性的定義是某階段的狀態一旦確定,則此後過程的演變不再受此前各種狀態及決策的影響。
  • 一文讀懂動態規划算法
    (3)有重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被多次使用到。(該性質並不是動態規劃適用的必要條件,但是如果沒有這條性質,動態規划算法同其他算法相比就不具備優勢)求解的基本步驟動態規劃所處理的問題是一個多階段決策問題,一般由初始狀態開始,通過對中間階段決策的選擇,達到結束狀態。
  • 五大常用算法:動態規划算法
    (該性質並不是動態規劃適用的必要條件,但是如果沒有這條性質,動態規划算法同其他算法相比就不具備優勢)四、求解的基本步驟動態規劃所處理的問題是一個多階段決策問題,一般由初始狀態開始,通過對中間階段決策的選擇,達到結束狀態。這些決策形成了一個決策序列,同時確定了完成整個過程的一條活動路線(通常是求最優的活動路線)。
  • 打家劫舍的出處、釋義、典故、近反義詞及例句用法 - 成語知識
    打家劫舍,舍:居住的房子。成群結夥地到別人家裡;用暴力搶奪財物。出自:元 武漢臣《玉壺春》第四折:「見倈子撅天撲地,不弱如打家劫舍殺人賊。」近義詞有:為非作歹、殺人越貨、趁火打劫、明火執杖,反義詞有:扶危濟困、劫富濟貧,打家劫舍是貶義成語,聯合式成語;可作主語、謂語、定語;含貶義,指搶劫。打家劫舍的詳細解釋:成語名稱:打家劫舍(dǎ jiā jié shè)成語釋義:舍:居住的房子。
  • 經典算法研究系列 之 動態規划算法
    (3)有重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被多次使用到。(該性質並不是動態規劃適用的必要條件,但是如果沒有這條性質,動態規划算法同其他算法相比就不具備優勢)求解的基本步驟動態規劃所處理的問題是一個多階段決策問題,一般由初始狀態開始,通過對中間階段決策的選擇,達到結束狀態。
  • 經典動態規劃:「換硬幣」系列三道問題詳解
    轉自面向大象編程換硬幣(Coin Change)問題是一道經典的動態規劃入門題,但是你可能不太知道,LeetCode 上的換硬幣問題其實是一個系列,共三道題目:第一道題是大家都熟悉的動態規劃入門題;第二道題變為求方案數,需要我們不重複不遺漏;第三道題更有難度,需要擴充為二維動態規劃,才能準確求出方案數量。沒有做過這個系列三道題的同學,不妨跟著本文看看三道題目的解法,理解其中的思路和技巧。下面我們一道一道地進行分析。