链接:198.打家劫舍
思路:
动态规划经典题目,首先定义下标:dp[i]表示打劫i+1个房屋可以获得的最大金额。初始化dp数组为0,dp[0]=nums[0]表示打劫1个房屋可以获得nums[0]的金额。然后确认递推公式,根据题意,无法打劫两间相邻的房屋,所以如果打劫了nums[i-1]的房屋,就不能打劫nums[i],所以dp[i]就需要在打劫nums[i-1]的房屋和nums[i]的房屋两者之间选一个最大值就可以最大化金额。如果打劫nums[i-1]的房屋,可以获得的最大金额为dp[i-1],如果打劫nums[i]的房屋,可以获得的最大金额为dp[i-2]+nums[i],那么递推公式为dp[i] = max(dp[i-2] + nums[i], dp[i-1])。遍历顺序为从i=2开始向后遍历,注意因为递推公式里有i-2所以i必须大于等于2。同时别忘了初始化dp[1]为max(nums[0], nums[1]),当只有两间房屋的时候取金额大的那个。最后返回dp[nums.size()-1]就行了。
代码:
class Solution {
public:
int rob(vector& nums) {
vector dp(nums.size());
dp[0] = nums[0];
if (nums.size() == 1)
return dp[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];
}
};
链接:213.打家劫舍II
思路:
相比于上一道题增加了一个限制条件,就是数组头尾的两个数组也算相邻的两间房,所以需要在打劫头间房屋/打劫尾间房屋之间选择一个,如何用dp记录这两种情况的最佳选择呢?只要用两个dp数组就可以了,两个数组分别记录范围[0, nums.size() - 2]和[1, nums.size() - 1]的房间,分别代表了打劫头间房屋和打劫尾间房屋,大小都为nums.size() -1。
初始化dp1和dp2:
两个dp数组的递推公式都是和之前的一样,唯一的区别是nums的下标不一样,dp2始终比dp1取的下标要靠后一位。最后在dp1和dp2的最后一位取一个最大值,表示偷第一间/偷末尾间所能获得的最大金额。
代码:
class Solution {
public:
int rob(vector& nums) {
if (nums.size() == 1)
return nums[0];
if (nums.size() == 2)
return max(nums[0], nums[1]);
// 第一个dp数组,范围[0, nums.size() - 2]
vectordp1(nums.size()-1);
// 第二个dp数组,范围[1, nums.size() - 1]
vectordp2(nums.size()-1);
dp1[0] = nums[0];
dp1[1] = max(nums[0], nums[1]);
dp2[0] = nums[1];
dp2[1] = max(nums[1], nums[2]);
for (int i = 2; i < nums.size() - 1; i++)
{
dp1[i] = max(dp1[i-2] + nums[i], dp1[i-1]);
dp2[i] = max(dp2[i-2] + nums[i + 1], dp2[i-1]);
}
return max(dp1[nums.size()-2], dp2[nums.size()-2]);
}
};
链接:337.打家劫舍III
思路:
这一次,房间的排列不再是数组了,而是一个二叉树。动态规划的做法和之前的一样,都是采用在遍历的时候比较打劫这一间还是前一间所能获得的最大金额,然后取更大的那个。遍历二叉树的时候,一定要选取后序遍历,这样才能够比较方便的进行比较。
用于遍历的递归函数postorder需要返回一个dp数组,只有两个元素,用于储存打劫当前房屋和打劫前一间房屋所能获得的最大金额。当遇到空节点时,返回{0, 0},因为空节点代表没有房间,所能获得的金额为0。然后分别用left、right两个数组表示左节点和右节点最大金额,按照和之前一样的逻辑,可以写出递推公式
然后将dp数组返回给上一个节点,最后在头节点取dp[0]和dp[1]中大的那个并返回,就是所能获取的最大金额。
代码:
/**
* 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) {
if (root == nullptr)
return 0;
vector dp = postorder(root);
return max(dp[0], dp[1]);
}
vector postorder(TreeNode* root)
{
if (root == nullptr)
return {0, 0};
vector left = postorder(root->left);
vector right = postorder(root->right);
// dp[0]: 不偷该节点能得到的最大金钱
// dp[1]: 偷该节点能得到的最大金钱
vector dp(2);
// 不偷该节点,能获得的金钱为左节点偷或不偷能获得的最大金钱,加上右节点偷或不偷能获得的最大金钱
dp[0] = max(left[0], left[1]) + max(right[0], right[1]);
// 偷该节点,能获得的金钱为该节点金钱,加上不偷两个子节点能获得的最大金钱
dp[1] = left[0] + right[0] + root->val;
return dp;
}
};