代碼隨想錄算法訓練營|第五十天|198.打家劫舍、213.打家劫舍II、337.打家劫舍III。刷题心得(c++)

目录

讀題

198.打家劫舍

自己看到题目的第一想法

看完代码随想录之后的想法

213.打家劫舍II

自己看到题目的第一想法

看完代码随想录之后的想法

337.打家劫舍III

自己看到题目的第一想法

看完代码随想录之后的想法

198.打家劫舍 - 實作

思路

Code

213.打家劫舍II - 實作

思路

Code

337.打家劫舍III - 實作

思路

Code

總結

自己实现过程中遇到哪些困难

今日收获,记录一下自己的学习时长

相關資料

第九章 动态规划part09


讀題

198.打家劫舍

自己看到题目的第一想法

這感覺是跟01 背包問題中的最大價值很像,但是因為相鄰的部分不能被rob,所以在做背包的遞迴時,需要將i += 2?

沒有搞得很清楚這個問題的含意,先看題解

看完代码随想录之后的想法

當前的屋子偷與不偷取決於前一個房子以及前兩個房子是否被偷了,需要先有這想法

之後去定義dp數組時,定義i 以內的房屋最高偷多少錢為dp[i]

遞推公式也要根據前面的思維

去看dp[i - 2] + 當前value比較高還是dp[i - 1]比較高,來取值

在根據遞推公式初始化dp[0] 跟 dp[1] 因為不能操控到空指針

213.打家劫舍II

自己看到题目的第一想法

看完我在想說初始化時,要改為三個因為避免前後都取,前三個只能取最大,因為環形的緣故,但是要去思考怎麼讓後面的數值也不要跟前面的相同,就有些犯難了,我不清楚怎麼樣才能夠避開這個環形

看完代码随想录之后的想法

很清晰,主要分三種狀況,首尾都不取、取首元素、取尾元素,分為這三種狀況

其實主要是把取首元素,取尾元素這兩種狀況分別出來,比較哪個比較大

至於狀況一,則是在狀況二、三時,就解決了。

337.打家劫舍III

自己看到题目的第一想法

這題讓我想到之前有一題是裝設監視器,也是分情況,二叉樹的動態規劃要怎麼做,很沒有想法,但大致上知道是說,是後序遍歷,選擇左右子樹比較有錢的部分,但感覺也不太對,先來看題解。

看完代码随想录之后的想法

雖然都是後續遍歷,但是是紀錄有沒有偷過

因為要用到二叉樹的遍歷,所以要使用遞迴

首先要確認傳入參數和返回值,返回的數組就是dp數組,傳入值則是樹節點

定義dp數組的含意,下標為0定為不偷該節點的最大金額,下標為1定為偷該節點的最大金額

dp數組的長度為2,因為遞迴的過程中,系統會保存每一層的參數

確認終止條件,假設遇到空節點,那都是0

確定遍歷順序,後續遍歷,透過遞回函樹的返回值來進行計算

透過遞歸左節點,得到左節點偷與不偷的金額

透過遞歸右節點,得到右節點偷與不偷的金額

確定單層遞歸的邏輯

偷當前節點左右孩子就不能偷,因為相鄰 va1 = cur→val + left[0] + right[0]

不偷當前節點,左右節點都可以偷,就是偷最大的 val2 = max(left[0] + left[1]) + max(right[0], right[1]);

遞推公式就會變成 max(val1 ,val2)

198.打家劫舍 - 實作

思路

  1. 定義DP數組以及下標的含意

    dp[i]: 在房屋i (包含i)之前,所能得到的最高金額是dp[i]。

  2. 遞推公式

    要偷這個房屋,還是不偷這個房屋,取決與前一個以及前兩個

    前一個房屋要偷,當下就不能偷,因為題意的緣故 dp[i - 1]

    當下的要偷,跳過前一個的房屋也要偷,因為題意是不能相鄰而已,dp[i - 2] + nums[i];

    所以dp 公式為: dp[i] = max ( dp[i - 1], dp[i -2] + nums[i]);

  3. 根據遞推公式,確定DP數組如何初始化

    dp[0] = nums[0]

    dp[1] = max(nums[0], nums[1])

    可以想成這是從哪個開始偷,起始金額比較高

  4. 確定遍歷順序

    因為是由前兩個數值所推出來的,所以是由前往後遍歷。

Code

class Solution {
public:
    int rob(vector& nums) {
        if(nums.size() == 1) return nums[0];
        vector dp (nums.size(), 0);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        for(int i = 2; i < nums.size(); i++) {
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return dp[nums.size() - 1];
    }
};

 

213.打家劫舍II - 實作

思路

  1. 定義DP數組以及下標的含意

    dp[i]: 在房屋i (包含i)之前,所能得到的最高金額是dp[i]。

  2. 遞推公式

    遞推公式跟打家劫舍依樣

    所以dp 公式為: dp[i] = max ( dp[i - 1], dp[i -2] + nums[i]);

  3. 根據遞推公式,確定DP數組如何初始化

    dp[start] = nums[start]

    dp[start] = max(nums[start], nums[start + 1])

    可以想成這是從哪個開始偷,起始金額比較高

  4. 確定遍歷順序

    主要有三種情況,這裡著重處理後兩種

    第一種,首尾都不取

    第二種,只取首數

    第三種,只取尾數

    遞推的部分都是由前往後,但主要是加上兩種狀況的分別遞推

Code

class Solution {
public:
    int rob(vector& nums) {
            if(nums.size() == 1) return nums[0];
            if(nums.size() == 2) return max(nums[0], nums[1]);
            int result1 = robResult(nums, 0, nums.size() - 2); //情況二
            int result2 = robResult(nums, 1, nums.size() - 1); //情況三
            return max(result1, result2);
    }
    int robResult(vector& nums, int start, int end) {
        vector dp (nums.size(), 0);
        dp[start] = nums[start];
        dp[start + 1] = max(nums[start], nums[start + 1]);
        for(int i = start + 2; i < nums.size(); i++) {
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return dp[end];
    } 
};

 

337.打家劫舍III - 實作

思路

  1. 確定傳入值與返回值
    1. 傳入值: treenode
    2. 返回值: dp數組
  2. dp數組定義
    1. 長度為二的數組,下標為0代表不偷該節點的最大金額,下標為1代表偷該節點的最大金額
  3. 確認終止條件
    1. 假設遇到空節點,返回{0, 0},代表偷與不偷都為0
  4. 確定樹的遍歷順序
    1. 後序遍歷,因為需要左右子樹的值來協助判斷
  5. 單層遞迴的邏輯
    1. 偷當前節點,左右孩子都不能偷,va1 = cur→val + left[0] + right[0]
    2. 不偷當前節點,左右孩子都可以偷,就是偷最大的 val2 = max(left[0] + left[1]) + max(right[0], right[1]);
  6. 遞推公式 max(val1, val2)

Code

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int rob(TreeNode* root) {
        vector result = robTree(root);
        return max(result[0], result[1]);
    }
    vector robTree(TreeNode* cur) {
        if (cur == NULL) return vector{0, 0};
        vector left = robTree(cur->left);
        vector right = robTree(cur->right);

        int val1 = cur->val + left[0] + right[0];
        int val2 = max(left[0],left[1]) + max(right[0], right[1]);
        return {val2, val1};
    }
};

 

總結

自己实现过程中遇到哪些困难

今天的三題,第一題因為之前做背包做太久,一開始就有點亂槍打鳥的意味,第二題則是沒有想到分為三種狀況分別處理,第三題有點像是第二題的延伸,不過這是第一題在動態規畫中使用到樹的概念,理解不難,但要自己寫出來就會遇到很大的問題

今日收获,记录一下自己的学习时长

大約學習了2hr左右,今天的三題都算是比較好理解的,但是自己之前沒有做過,所以在做的過程中很卡,但所幸都有初步理解了。

相關資料

● 今日学习的文章链接和视频链接

第九章 动态规划part09

198.打家劫舍

视频讲解:动态规划,偷不偷这个房间呢?| LeetCode:198.打家劫舍_哔哩哔哩_bilibili

https://programmercarl.com/0198.打家劫舍.html

213.打家劫舍II

视频讲解:动态规划,房间连成环了那还偷不偷呢?| LeetCode:213.打家劫舍II_哔哩哔哩_bilibili

https://programmercarl.com/0213.打家劫舍II.html

337.打家劫舍III

视频讲解:动态规划,房间连成树了,偷不偷呢?| LeetCode:337.打家劫舍3_哔哩哔哩_bilibili

https://programmercarl.com/0337.打家劫舍III.html

你可能感兴趣的:(算法,c++,数据结构,动态规划,开发语言)