leecode刷題-20200529-easy-198.打家劫舍

聲明:
作者不是什麼大佬,只是想寫寫算法,提高一下自己的內功。所以代碼可能會非常凌亂,(大佬們就當個笑話看就可以了),但我會認真註釋。


最後如果有路過的大佬,希望可以留下你們的建議和看法,謝謝!

198.打家劫舍

一、原題鏈接

198.打家劫舍

二、題目介紹

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

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

三、測試用例

1.示例

輸入: [1,2,3,1]
輸出: 4
解釋: 偷竊 1 號房屋 (金額 = 1) ,然後偷竊 3 號房屋 (金額 = 3)。
偷竊到的最高金額 = 1 + 3 = 4 。

2.示例

輸入: [2,7,9,3,1]
輸出: 12
解釋: 偷竊 1 號房屋 (金額 = 2), 偷竊 3 號房屋 (金額 = 9),接着偷竊 5 號房屋 (金額 = 1)。
偷竊到的最高金額 = 2 + 9 + 1 = 12 。

四、思路解析

這道題我是看着題的解析寫的,才知道什麼叫做動態規劃

知乎一個講動態規劃比較好的回答,建議看過之後,再去看看題,看看自己有沒有想法
什麼是動態規劃


使用動態規劃時,我認爲的核心就用當前的最優解進行判斷,並且不去詢問最優解的得來(最優解的過程),只看最優解的值
可能有的人回想,不管過程怎麼求解呢?
解答:最優解的值是肯定要通過過程得來的,但是在我們抽象到每一碎片的時候,我們可以使用這個未知的最優解進行解答當前的最優解,而程序調用會不斷深入未知的最優解一步一步的解析出來

五、代碼

1.遞歸調用(遞歸算法超時,結果正確)

在這裏插入圖片描述
我們先列出方程dp(i) = max(dp(i-1), dp(i-2)+a[i])

  • 這裏我們“從右往左數”,當小偷偷盜第i個屋子的時候,看他偷還是不偷,是要看兩種情況
  • 偷。偷第i間屋子,說明偷之前的最優解是在 i-2 的時候最優的結果,不會報警
  • 不偷。 前提前面的“屋子”可能我已經偷過了,第i間不能偷了,否則我肯定偷

這個時候可能有的同學要提問了,這個邏輯在某種情況上看來是不成立的。
比如:[1,9,1,1,9,1]
當數組爲這種情況的時候我們發現最優解是偷兩個,但是結合不偷的情況, 我們判斷第4個位置上的1時,發現不滿足不偷的情況,算法應該是有問題的。但是注意:
注意動態規劃不用知道dp(k),第k步的時候不用去了解怎麼實現到的,把之前的看作一個整體判斷現在的就可以,對應到這裏那就是[1 9 1 1]雖然是10,但是新的最優解9是在[1 9 1 1 9]的情況下繼續向左重新計算的結果,這個邏輯就是上述的方程。

	// index是指當前的nums的 i值
	// 整體是從右向左進行,但是程序實際的調用會通過遞歸深入到i=0開始判斷
   public int stole(int[] nums, int index) {
		// 邊界問題(可以化簡)
         if(nums==null || nums.length==0){
             return 0;
         }
         if(nums.length == 1){
             return nums[0];
         }
         if(nums.length == 2){
             return nums[0]>=nums[1] ? nums[0] : nums[1];
         }
		// 遞歸的動態規劃的主體部分
          // 說明遞歸到了最左位置,此時的最優解只能是nums[0]
          if (index == 0 ) {
             return nums[index];
         }
          // 說明遞歸到了第二位置,此時的最優解需要通過與第一位的大小進行判斷 
         else if (index == 1) {
             return nums[0] >= nums[1] ? nums[0] : nums[1];
         }
          // i>=2 時的普遍算法 
         else {
         	// dp方程的使用
         		// 偷 說明在i-2應該已經偷了,那麼i的最優解就應該是i-2 + i 形成新的最優解
             int yes = nums[index] + stole(nums, index - 2);
             	// 不偷 說明在i-1就已經偷了,那麼i的最優解就是i-1的最優解
             int no = stole(nums, index - 1);
             // 返回i的最優解,會因爲上面的遞歸依此進入進行計算
             return yes >= no ? yes : no;
         }
     }
	// 使用遞歸從右往左
     public int rob(int[] nums) {
         int i = stole(nums, nums.length - 1);
         return i;
     }
1.循環調用(算法正確)

本算法的思路與上面的一致,只是在表達上有所不同。上一算法主要受到了,知乎例子的影響習慣性從大計算,但是拆分上面的遞歸邏輯,其實還是從i=0開始計算的。
核心還是:dp(i) = max(dp(i-1), dp(i-2)+a[i]),在計算第i像最優解的時候要考慮i-1和i-2處的最優解是什麼

	// 使用循環從左向右
	public int rob(int[] nums) {
		// 邊界問題
        if (nums == null || nums.length == 0) {
            return 0;
        }
        if (nums.length == 1) {
            return nums[0];
        }
        if (nums.length == 2) {
            return nums[0] > nums[1] ? nums[0] : nums[1];
        }
		// 循環主體
        int n = nums.length;
        // i-1的最優解
        int last = nums[0] > nums[1] ? nums[0] : nums[1];
        // i-2的最優解
        int last_last =nums[0];
        for (int i = 2; i < n; i++) {
            // 計算不偷時的當前最優解 和 偷時當前的最優解
            int temp = Math.max(last, last_last+nums[i]);
            // 更改
            last_last = last;
            last = temp;
        }
        return last;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章