任务日期:7.15
题目一链接:198. 打家劫舍 - 力扣(LeetCode)
思路:先定义一个dp[i]数组:下标为i之前的房子最多能偷dp[i]的钱。然后确定dp数组的推导公式:dp[i]取决于前一个房子偷不偷:dp[i] = max(dp[i - 1],dp[i - 2] + nums[i])。接着初始化dp,最后确定遍历顺序。
代码:
class Solution {
public:
int rob(vector<int>& nums) {
//if(nums.size() == 0) return 0;
if(nums.size() == 1) return nums[0];
//初始化dp[i]:下标为i之前的房屋,最多可偷dp[i]的钱
vector<int> dp(nums.size() + 1,INT_MIN);
//递推公式
//dp[i] = max(dp[i - 1],dp[i - 2] + nums[i]);
//初始化
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];
}
};
难点:1.处理边界问题当nums的大小等于1时,直接返回nums[0]
2.dp数组初始化的值是最小值,因为后面要取最大值。
3.dp数组的递推公式中的i必须从2开始,所以i等于0和1时必须提前初始化。
题目二链接:213. 打家劫舍 II - 力扣(LeetCode)
思路:本题可以将环拆分成不包含首或者尾的两个普通的打家劫舍问题,并且记录这两个值,最后返回最大值。
代码:
class Solution {//环跟普通数组的区别在于首和尾有一个可以不用考虑,这样算出两个result,并且比较两个result的大小,取最大值
public:
int rob(vector<int>& nums) {
//处理边界问题
if(nums.size() == 1) return nums[0];
//以下两中情况都包括即不含头也不含尾的情况
int result1 = robrange(nums,0,nums.size() - 2);//不包含尾
int result2 = robrange(nums,1,nums.size() - 1);//不包含首
return max(result1,result2);
}
//打家劫舍模版:
int robrange(vector<int>& nums,int start,int end) {//此时数组的开头是start
//如果就一个房子这接返回
if(start == end) return nums[start];
//确定dp【i】数组:在下表为i的房间中,最多能偷dp[i]的钱
vector<int> dp(nums.size() + 1,INT_MIN);
//dp数组的初始化
dp[start] = nums[start];
dp[start + 1] = max(nums[start],nums[start + 1]);
//dp[i] = max(dp[i - 1],dp[i - 2] + nums[i]);
//遍历顺序
for(int i = start + 2;i <= end;i ++) {//for循环对dp数组便利从第三个数开始
dp[i] = max(dp[i - 1],dp[i - 2] + nums[i]);
}
return dp[end];
}
};
难点:1.为什么不考虑既不包含首又不包含尾的情况:因为这两个情况都包括此情况。
2.首先需要考虑边界问题:start == end
3.dp数组的递推公式必须从第三个值开始,因此前两个值需要提前初始化。
题目三链接:337. 打家劫舍 III - 力扣(LeetCode)
思路:本题也是一个树形dp。定义一个只含有两个值(一个偷当前节点所得最大金钱,一个不偷所得的最大金钱)的dp数组,更新每个节点的这俩值,最后返回根节点的这俩值的最大值。
代码:
/**
* 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> result = robTree(root);//调用递归函数,求当前节点的两个值
return max(result[0],result[1]);//返回含两个元素的dp数组的最大值
}
//确定递归函数的返回值和参数
// 长度为2的数组,0:不偷,1:偷
vector<int> robTree(TreeNode* cur) {
//确定递归终止条件:遇见空节点就返回
if(cur == nullptr) return {0,0};
//确定递归顺序:后序遍历,因为要通过左孩子和右孩子的递归函数的返回值来做下一步计算
vector<int> left = robTree(cur -> left);//通过递归左节点,得到左节点偷与不偷的最大金钱
vector<int> right = robTree(cur -> right);
//中
//确定单层递归逻辑
//偷cur:
int val1 = cur -> val + left[0] + right[0];//系统栈会保存每一层递归的参数
//不偷cur:
int val2 = max(left[0],left[1]) + max(right[0],right[1]);
return {val2,val1};//弹出栈中元素给当前层
}
};
难点:1.就是在更新dp数组的基础上加上遍历树的操作。递归三部曲融合动规五部曲
2.确定递归顺序时要后序遍历同时还要记录左节点的偷与不偷的金钱最大值。
3.中节点不用进行操作
4.确定单层递归逻辑时,不偷的时候他的两个子节点不一定都偷而是分别取最大值;**每个节点更新完两个值以后还需要弹出栈中这两个值给当前层。**
解释细节2:因为需要左右孩子的递归函数的返回值才能进行下面的操作。
解释细节4:系统栈会自动保存当前递归层中的参数,return参数是返回给当前层。