打家劫舍系列(dp)
引言
打家劫舍類的題目是動態規劃里的經典題目,有很多種變換形式,下面就介紹一下力扣里面的所有打家劫舍的題目;
打家劫舍
你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
給定一個代表每個房屋存放金額的非負整數數組,計算你 不觸動警報裝置的情況下 ,一夜之內能夠偷竊到的最高金額。
示例 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 。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 400
我們來分析一下這道題:
1,dp[i]代表 i 以內的房屋偷盜的最大金額;
2,如果在第 i 間房屋偷錢了,那么dp[i] = dp[i - 2] + nums[i];
如果沒有在第 i 間房屋偷錢,那么dp[i] = dp[i - 1];
因為求最大值,所以dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
3,dp數組初始化無非就兩種情況
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
4,因為dp[i]是由dp[i - 1]和dp[i - 2]求得的,所以可以看出來遍歷順序就是從前到后遍歷的;
代碼如下:
class Solution { public:int rob(vector<int>& nums) {int n = nums.size();if (n == 0) return 0;if (n == 1) return nums[0];vector<int> dp(n);dp[0] = nums[0];dp[1] = max(nums[0], nums[1]);for (int i = 2; i < n; ++i) {dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);}return dp[n - 1];} };這是打家劫舍1.0基礎版,比較容易求出來,接下來我們來個升級版的;
打家劫舍II
你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有一定的現金。這個地方所有的房屋都 圍成一圈 ,這意味著第一個房屋和最后一個房屋是緊挨著的。同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警 。
給定一個代表每個房屋存放金額的非負整數數組,計算你 在不觸動警報裝置的情況下 ,能夠偷竊到的最高金額。
示例 1:
輸入:nums = [2,3,2]
輸出:3
解釋:你不能先偷竊 1 號房屋(金額 = 2),然后偷竊 3 號房屋(金額 = 2), 因為他們是相鄰的。
示例 2:
輸入:nums = [1,2,3,1]
輸出:4
解釋:你可以先偷竊 1 號房屋(金額 = 1),然后偷竊 3 號房屋(金額 = 3)。偷竊到的最高金額 = 1 + 3 = 4 。
示例 3:
輸入:nums = [0]
輸出:0
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 1000
這道題里的房屋都圍成了一個圈,這樣和第一題不同的就是需要多考慮一下,
情況1:如果偷了第一個房屋,最后一個就不能偷;
情況2:如果沒有偷第一個房屋(那就是從第二個房屋開始偷的),就可以偷最后一個;
可以發現單看哪種情況都是和打家劫舍1.0版本一樣的;
所以我們需要單獨求出兩種情況的最大金額,然后取最大值即可;
為了方便求兩種情況最大金額,再額外寫個函數更好;
代碼如下:
class Solution { public:int rob(vector<int>& nums) {int n =nums.size();if (n == 1) return nums[0];if (n == 2) return max(nums[1], nums[0]);//情況2和情況1return max(robRank(nums, 1, n - 1), robRank(nums, 0, n - 2));}int robRank(vector<int>& nums, int begin, int end) {vector<int> dp(nums.size());dp[begin] = nums[begin];dp[begin + 1] = max(nums[begin], nums[begin + 1]);for (int i = begin + 2; i <= end; ++i) {dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);}return dp[end];} };這道題就需要多考慮一下了,但是核心還是一樣的;接下來來個不一樣的
打家劫舍III
在上次打劫完一條街道之后和一圈房屋后,小偷又發現了一個新的可行竊的地區。這個地區只有一個入口,我們稱之為“根”。除了“根”之外,每棟房子有且只有一個“父“房子與之相連。一番偵察之后,聰明的小偷意識到“這個地方的所有房屋的排列類似于一棵二叉樹”。如果兩個直接相連的房子在同一天晚上被打劫,房屋將自動報警。
計算在不觸動警報的情況下,小偷一晚能夠盜取的最高金額。
示例1
示例2
這是一道樹形dp的入門題,相似的題目,需要我們熟悉二叉樹的遍歷,而這道題需要用到的是后續遍歷;
我們假設 1 為偷, 0 為不偷,那么我們需要一個遞歸函數,返回偷和不偷的金額即可,最后區它倆的最大值就是本題答案;
所以我們可以先確定返回值為一個vector數組,大小為2
那么這個遞歸函數的終止條件又是什么?
當傳入節點 root 為空時,我們就返回{0, 0}就可以了;
接下來我們定義兩個vector變量存放遞歸左子樹和右子樹的結果
vector<int> l = robTree(root -> left); vector<int> r = robTree(root -> right);最后我們就該確定遞歸邏輯了,這里分為兩種情況
1,如果偷root,則左右孩子就不能偷
2,如果不偷root,那么左右孩子都可以偷,但是偷或者不偷需要在這里選擇一個最大值;
int ans2 = max(l[1], l[0]) + max(r[1], r[0]);最后返回ans2和ans1就可以了,一定要注意返回時的順序,不偷的在前,偷的在后
代碼如下:
/*** 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<int> ans = robTree(root);return max(ans[0], ans[1]);}// 長度為2的數組,0不偷,1偷vector<int> robTree(TreeNode* root) {if (!root) return vector<int>{0, 0};vector<int> l = robTree(root -> left);vector<int> r = robTree(root -> right);int ans1 = root -> val + l[0] + r[0]; //偷rootint ans2 = max(l[1], l[0]) + max(r[1], r[0]); //不偷rootreturn {ans2, ans1};} };總結
打家劫舍系列就結束了,這里三道題都是力扣原題,最后一道題用到了樹形dp,所以需要多了解一下,前兩道題目都很相似,都可以練練手,有什么問題也歡迎交流;
總結
以上是生活随笔為你收集整理的打家劫舍系列(dp)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 接雨水(动态规划)
- 下一篇: 买股票的最佳时机(六种题解dp)