一、前言
参考文献:代码随想录
今天的主题是打家劫舍,动态规划;
首先回忆动态规划的步骤:
(1)创建dp数组;
(2)确定递推公式;
(3)初始化;
(4)遍历顺序;
(5)打印dp数组;
二、打家劫舍
1、思路:
我一开始的思路是打算用背包思路做的,,,发现现在已经被背包洗脑了。。
越做越麻烦,然后就看了卡哥的文章,豁然开朗;
首先我复述一下最重要的递推公式:
(1)房子分为两种状态:被偷、不被偷;
当被偷时,它的邻接就不会被偷了,当不被偷是他的邻居可能更富;
所以我们围绕这个来创建了一个dp数组dp[i]:在第i家屋子所能获得的最大财物为dp[i];
当我们偷当前这家,那么我们邻居就不能再偷了:dp[i -2] + nums[i](价值);
如果我们不偷这一家,那么上一家就会被偷: dp[i - 1];
对,就是这样;
(2)然后就是初始化了:
我们发现dp[i]是有前面推出来的(遍历顺序)所以我们需要初始化前两个值;
dp[0]很显然等于nums[0],那dp[1]呢?
那就是nums[0] and nums[1]之间的最大值啦;
2、整体代码如下:
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 1) return nums[0];
if (nums.size() == 0) return 0;
// 1、创建dp数组
vector<int> dp(nums.size(), 0);
// 2、初始化
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
// 3、遍历顺序
for (int i = 2; i < nums.size(); i++) {
// 4、递推公式
// 分为这个房间偷还是不投,取最大值
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
// 5、打印dp数组
for (auto i : dp) {
cout << i << " ";
}
cout << endl;
return dp[nums.size() - 1];
}
};
三、打家劫舍||
1、思路:
与上一题差不多,但是需要分为三种情况:
(1)不包含头部和尾部:那就和198如出一辙了。
(2)包含头部,不包含尾部,那也可以用198的思路去做,就不会成环;
(3)包含尾部,不包含头部,也是198的思路。
第一种情况被第二三种情况包含了,所以只需要做两种情况就可以了。可以直接通过指针来确定头和尾。
2、整体代码如下:
class Solution {
public:
int rob(vector<int>& nums) {
int size = nums.size();
if (size == 1) return nums[0];
if (size == 0) return 0;
int max2 = robRange(nums, 0, size - 2);
int max3 = robRange(nums, 1, size - 1);
return max(max2, max3);
}
int robRange(vector<int>& nums, int start, int end) {
if (end == start) return nums[end];
vector<int> dp(nums.size(), 0);
dp[start] = nums[start];
dp[start + 1] = max(nums[start + 1], nums[start]);
for (int i = start + 2; i <= end; i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[end];
}
};
四、打家劫舍|||
1、思路:
比较抽象,然是掌握了树形思维方式的话,就可以解决了;
(1)首先本题使用的是递归+dp来解决的,在递归中穿插着dp。首先是dp数组,其实这里的dp数组与其说是dp数组,更可以把他看作一个标记:分为dp[2],0代表不偷该节点,1代表偷该节点;每一层递归穿插着这样的dp数组,他的值就是有没有偷该节点的值。
(2)其次就是递归三部曲了:
1-参数和返回值:
vector<int> robTree(TreeNode* cur)
这里就是返回的是dp数组,参数不用多说了;
2-终止条件:
if (cur == NULL) return vector<int>{0, 0};
遍历到空节点就返回了,返回的就是没有价值的dp;
3-遍历顺序就是后续遍历,从叶子节点开始遍历,才能够知道孩子节点有没有值,从而推到根节点的大小,判断根节点取还是不取:
// 左
vector<int> leftdp = robTree(cur->left);
// 右
vector<int> rightdp = robTree(cur->right);
// 中
int val_get = cur->val + leftdp[0] + rightdp[0];
int val_lose = max(leftdp[0], leftdp[1]) + max(rightdp[0], rightdp[1]);
return vector<int> {val_lose, val_get};
2、整体代码如下:
class Solution {
private:
vector<int> robTree(TreeNode* cur) {
// 这里的dp表示的是0为偷,1为不偷
// 终止条件
if (cur == NULL) return vector<int>{0, 0};
// 左
vector<int> leftdp = robTree(cur->left);
// 右
vector<int> rightdp = robTree(cur->right);
// 中
int val_get = cur->val + leftdp[0] + rightdp[0];
int val_lose = max(leftdp[0], leftdp[1]) + max(rightdp[0], rightdp[1]);
return vector<int> {val_lose, val_get};
}
public:
int rob(TreeNode* root) {
// 看完卡哥的讲解,一个字:绝顶
vector<int> dp = robTree(root);
return max(dp[0], dp[1]);
}
};
Time to Study: 2h.
leave message:
Don't judge each day by the harvest you reap but by the seeds taht you plant.
不要以收获来评判每一天,而要以你播下的种子来评判。