(本文題源LeetCode)
LeetCode上有好多題目是有前后關系的,今天我們就來講一下“打家劫舍”三道題的解法。這三道題運用到了動態規劃以及樹的思想。
打家劫舍I
你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。
給定一個代表每個房屋存放金額的非負整數數組,計算你不觸動警報裝置的情況下 ,一夜之內能夠偷竊到的最高金額。
顯然這道題考察的是動態規劃。由于我們不能同時偷相鄰兩個房屋的現金,所以要考慮兩種情況:
如果偷取第i個房屋的現金,則不能偷取第i-1個房屋的
如果不偷取第i個房屋的現金,則可以偷取第i-1個房屋的
也就是說,如果我們用num[i]來記錄第i個房屋里的現金數、用dp[i]來記錄前i個房屋可以偷取的最大現金數,則有
i=0時,dp[i]=num[i]
i≠0時,dp[i]=max(dp[i - 2] + nums[i], dp[i - 1])
代碼示例如下:
class Solution {
public:
int rob(vector<int>& nums) {
vector<int> dp(nums.size(), 0);
if (nums.size() == 0)return 0;
if (nums.size() == 1)return nums[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 - 2] + nums[i], dp[i - 1]);
return dp[nums.size() - 1];
}
};
打家劫舍II
你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有一定的現金。這個地方所有的房屋都 圍成一圈 ,這意味著第一個房屋和最后一個房屋是緊挨著的。同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警 。
給定一個代表每個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的情況下 ,今晚能夠偷竊到的最高金額。
簡單來說,相比于第一題,不同之處在于前一題房屋為行排列,這一題為圈排列。還是動態規劃。
既然有第一題的存在,為何不利用一下?行排列和圈排列之間可以怎么轉換呢?既然相鄰兩個房屋不能同時取,豈不是意味著可以在圈中任取相鄰兩個點,將兩點分別作為行排列的開頭,取兩種行排列的最大值。這樣就可以把圈排列轉換成行排列了。
class Solution {
public:
int linerob(vector<int>& nums) {
vector<int> dp(nums.size(), 0);
if (nums.size() == 0)return 0;
if (nums.size() == 1)return nums[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 - 2] + nums[i], dp[i - 1]);
return dp[nums.size() - 1];
}
int rob(vector<int>& nums) {
if (nums.size() <= 2)return linerob(nums);
vector<int> num1(nums.begin() + 1, nums.end());
vector<int> num2(nums.begin(), nums.end() - 1);
return max(linerob(num1), linerob(num2));
}
};
打家劫舍III
在上次打劫完一條街道之后和一圈房屋后,小偷又發現了一個新的可行竊的地區。這個地區只有一個入口,我們稱之為“根”。 除了“根”之外,每棟房子有且只有一個“父“房子與之相連。一番偵察之后,聰明的小偷意識到“這個地方的所有房屋的排列類似于一棵二叉樹”。 如果兩個直接相連的房子在同一天晚上被打劫,房屋將自動報警。
計算在不觸動警報的情況下,小偷一晚能夠盜取的最高金額。
輸入樣例
輸入: [3,4,5,1,3,null,1]
3
/
4 5
/ \ \
1 3 1
輸出: 9
解釋: 小偷一晚能夠盜取的最高金額 = 4 + 5 = 9.
在前兩道題的基礎上,這道題更進了一步,應用到了樹的結構。不要看到題目就慌了,其實思路和前面兩道題是差不多的。
父、子結點不能同時偷取,這和前面的相鄰兩個點不能同時偷取同樣道理。與前兩道題不同的是,我們需要兩個數組來記錄偷取現金的最大值,考慮是否偷取該結點的現金最大值分別是多少。
p,up分別記錄偷取以及不偷取該結點能獲得的最大值
如果偷取該結點,則其子節點不能偷取,即
p[node] = node->val + up[node->left] + up[node->right]
如果不偷取該結點,則其子節點可偷可不偷,即
up[node] = max(p[node->left], up[node->left]) + max(p[node->right], up[node->right])
思路很簡單,直接上代碼
/**
* 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:
unordered_map <TreeNode*, int> p, up;//pick,unpick
int rob(TreeNode* root) {
dfs(root);
return max(p[root], up[root]);
}
void dfs(TreeNode* node) {
if (!node)return;
dfs(node->left);
dfs(node->right);
p[node] = node->val + up[node->left] + up[node->right];
up[node] = max(p[node->left], up[node->left]) + max(p[node->right], up[node->right]);
}
};
以上就是本人對打家劫舍三道題簡單粗暴的思路,僅供參考。
更多大佬解法可以去LeetCode評論區學習學習。