1.确定dp数组以及下标的含义
dp[i] : 考虑下标i(包括i)以内的房屋,最多可以偷窃的金额数为dp[i]
2.确定递推公式
如果偷第i房间,dp[i] = dp[ i -2 ] + nums[i]。i - 1 房间不偷,下标i -2 (包括i - 2)以内的房间最多可以偷的金额为dp[i -2]加上第i房间偷的钱。
如果不偷第i房间,此时dp[i] = dp[ i -1]。
递推公式: dp[i] = max(dp[ i - 2]+ nums[i], dp[i - 1]);
3.dp数组如何初始化
由递推公式: dp[i] = max(dp[ i - 2]+ nums[i], dp[i - 1]); 递推公式的基础是dp[0]、dp[1]。
由dp的含义可知dp[0] = nums[0], dp[1] = max(nums[0] + nums[1])
初始化如下:
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0],nums[1]);
4.确定遍历顺序
dp[i] 是由dp[i-1]和dp[i-2]推导出来的,那么一定是从前到后遍历。
for(int i = 2; i < nums.size() ; i++){
dp[i] = max(dp[i-2] + nums[i], dp[i -1]);
}
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() == 0) return 0; // 当数组为空时,直接返回 0
if(nums.size() == 1) return nums[0]; // 当数组只有一个元素时,直接返回该元素的值
vector<int> dp(nums.size()); // 定义 dp 数组,长度为 nums 数组的长度
dp[0] = nums[0]; // 初始化 dp[0] 为 nums[0]
dp[1] = max(nums[0], nums[1]); // 初始化 dp[1],选择 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]; // 返回 dp 数组最后一个元素的值,即为所求
}
};
1.考虑不包含首尾元素
2.考虑包含首元素,不包含尾元素
3.考虑包含尾元素,不包含首元素
class Solution {
public:
int rob(vector<int>& nums) {
// 如果数组为空,直接返回0
if(nums.size() == 0) return 0;
// 如果数组长度为1,直接返回该数值
if(nums.size() == 1) return nums[0];
// 情况二:偷取[0, n-2]范围内的房屋
int result1 = robRange(nums,0,nums.size() - 2);
// 情况三:偷取[1, n-1]范围内的房屋
int result2 = robRange(nums,1,nums.size() - 1);
// 取两种情况的最大值
return max(result1,result2);
}
// 198题:打家劫舍问题
int robRange(vector<int>& nums, int start, int end){
// 如果范围内只有一个房屋,直接返回该数值
if(end == start) return nums[start];
// 初始化dp数组,dp[i]表示偷取[0,i]范围内的房屋所能获得的最大收益
vector<int> dp(nums.size());
dp[start] = nums[start];
// 计算dp[1]
dp[start+1] = max(nums[start],nums[start+1]);
// 遍历数组,计算dp[i]
for(int i = start+2; i<=end; i++){
// 当偷取第i个房屋时,偷取范围只能是[0, i-2],因此dp[i] = dp[i-2] + nums[i]
// 当不偷取第i个房屋时,偷取范围是[0, i-1],因此dp[i] = dp[i-1]
dp[i] = max(dp[i-2] + nums[i], dp[i-1]);
}
// 返回偷取[start, end]范围内的房屋所能获得的最大收益
return dp[end];
}
};
1.确定递归函数的参数和返回值
一个节点,偷or不偷。返回值为长度为2的数组。
vector<int> robTree(TreeNode* cur){}
dp数组以及下标的含义:下标0表示不偷该节点获得的最大钱数,1表示偷该节点获得的最大钱数。
本题的dp数组是一个长度为2的数组。
2.确定终止条件
遍历过程中遇到空节点,返回。
if(cur == NULL) return vector<int>(0, 0);
3.确定遍历顺序
后序遍历。
// 下标0:不偷 下标1:偷
vector<int> left = robTree(cur->left);// 左
vector<int>right = robTree(cur->right);// 右
// 中
4.确定单层递归的逻辑
当前节点偷,由题意。左右孩子不能偷。val1= cur->val + left[0] + right[0];
如果不偷当前节点,左右孩子可偷。val2 = max(left[0],left[1]) + max(right[0],right[1]);
最后当前节点状态{val2,val1};{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}
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};
class Solution {
public:
int rob(TreeNode* root) {
vector<int> result = robTree(root); // 获取最终结果
return max(result[0], result[1]); // 返回最大值
}
// 辅助函数,返回一个长度为2的数组,表示在当前节点为根节点时偷或不偷的最大值
vector<int> robTree(TreeNode* cur) {
if (cur == NULL) return vector<int>{0, 0}; // 空节点返回0
vector<int> left = robTree(cur->left); // 递归处理左子树
vector<int> right = robTree(cur->right); // 递归处理右子树
int val1 = cur->val + left[0] + right[0]; // 偷当前节点,左右子树都不能偷
int val2 = max(left[0], left[1]) + max(right[0], right[1]); // 不偷当前节点,左右子树可偷可不偷
return {val2, val1}; // 返回结果
}
};
[代码随想录]