目录
讀題
198.打家劫舍
自己看到题目的第一想法
看完代码随想录之后的想法
213.打家劫舍II
自己看到题目的第一想法
看完代码随想录之后的想法
337.打家劫舍III
自己看到题目的第一想法
看完代码随想录之后的想法
198.打家劫舍 - 實作
思路
Code
213.打家劫舍II - 實作
思路
Code
337.打家劫舍III - 實作
思路
Code
總結
自己实现过程中遇到哪些困难
今日收获,记录一下自己的学习时长
相關資料
第九章 动态规划part09
這感覺是跟01 背包問題中的最大價值很像,但是因為相鄰的部分不能被rob,所以在做背包的遞迴時,需要將i += 2?
沒有搞得很清楚這個問題的含意,先看題解
當前的屋子偷與不偷取決於前一個房子以及前兩個房子是否被偷了,需要先有這想法
之後去定義dp數組時,定義i 以內的房屋最高偷多少錢為dp[i]
遞推公式也要根據前面的思維
去看dp[i - 2] + 當前value比較高還是dp[i - 1]比較高,來取值
在根據遞推公式初始化dp[0] 跟 dp[1] 因為不能操控到空指針
看完我在想說初始化時,要改為三個因為避免前後都取,前三個只能取最大,因為環形的緣故,但是要去思考怎麼讓後面的數值也不要跟前面的相同,就有些犯難了,我不清楚怎麼樣才能夠避開這個環形
很清晰,主要分三種狀況,首尾都不取、取首元素、取尾元素,分為這三種狀況
其實主要是把取首元素,取尾元素這兩種狀況分別出來,比較哪個比較大
至於狀況一,則是在狀況二、三時,就解決了。
這題讓我想到之前有一題是裝設監視器,也是分情況,二叉樹的動態規劃要怎麼做,很沒有想法,但大致上知道是說,是後序遍歷,選擇左右子樹比較有錢的部分,但感覺也不太對,先來看題解。
雖然都是後續遍歷,但是是紀錄有沒有偷過
因為要用到二叉樹的遍歷,所以要使用遞迴
首先要確認傳入參數和返回值,返回的數組就是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)
定義DP數組以及下標的含意
dp[i]: 在房屋i (包含i)之前,所能得到的最高金額是dp[i]。
遞推公式
要偷這個房屋,還是不偷這個房屋,取決與前一個以及前兩個
前一個房屋要偷,當下就不能偷,因為題意的緣故 dp[i - 1]
當下的要偷,跳過前一個的房屋也要偷,因為題意是不能相鄰而已,dp[i - 2] + nums[i];
所以dp 公式為: dp[i] = max ( dp[i - 1], dp[i -2] + nums[i]);
根據遞推公式,確定DP數組如何初始化
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
可以想成這是從哪個開始偷,起始金額比較高
確定遍歷順序
因為是由前兩個數值所推出來的,所以是由前往後遍歷。
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];
}
};
定義DP數組以及下標的含意
dp[i]: 在房屋i (包含i)之前,所能得到的最高金額是dp[i]。
遞推公式
遞推公式跟打家劫舍依樣
所以dp 公式為: dp[i] = max ( dp[i - 1], dp[i -2] + nums[i]);
根據遞推公式,確定DP數組如何初始化
dp[start] = nums[start]
dp[start] = max(nums[start], nums[start + 1])
可以想成這是從哪個開始偷,起始金額比較高
確定遍歷順序
主要有三種情況,這裡著重處理後兩種
第一種,首尾都不取
第二種,只取首數
第三種,只取尾數
遞推的部分都是由前往後,但主要是加上兩種狀況的分別遞推
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];
}
};
/**
* 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左右,今天的三題都算是比較好理解的,但是自己之前沒有做過,所以在做的過程中很卡,但所幸都有初步理解了。
● 今日学习的文章链接和视频链接
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