打家劫舍
题目链接:力扣
- 确定dp数组(dp table)以及下标的含义
dp[i]:截至到i位置的房屋,最多可以偷窃的金额为dp[i]。 - 确定递推公式
决定dp[i]的关键因素就是第i房间偷还是不偷。
如果偷第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]取最大值,即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]); - dp数组如何初始化
dp[0] 一定是 nums[0]
dp[1] = max(nums[0], nums[1]); - 确定遍历顺序
dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,所以遍历顺序一定是从前到后遍历 - 举例推导dp数组
class Solution {
public:
int rob(vector<int>& nums) {
vector<int>dp(nums.size()+1); //dp表示截至到下标i,最多可以偷窃的金额
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++)
//如果第i间房不偷,dp[i] = dp[i-1]; 如果偷,则dp[i] = dp[i-2]+num[i]
dp[i] = max(dp[i-1], dp[i-2]+nums[i]);
return dp[nums.size()-1];
}
};
打家劫舍II
题目链接:力扣
与上题不同的是,这道题nums形成了环状
对于一个数组,成环的话主要有如下三种情况:
- 情况一:考虑不包含首尾元素
- 情况二:考虑包含首元素,不包含尾元素
- 情况三:考虑包含尾元素,不包含首元素
其中情况二和情况三包含了情况一,因为虽然考虑包含首或尾元素,但不一定要选首或尾部元素
则此题和题一就很类似了
class Solution {
public:
int rob(vector<int>& nums) {
vector<int>dp(nums.size()+1);
dp[0] = nums[0];
if(nums.size() == 1)
return dp[0];
return max(robrange(nums,0,nums.size()-2), robrange(nums,1,nums.size()-1));
}
int robrange(vector<int>& nums, int begin, int end)
{
if(begin == end) return nums[begin];
vector<int>dp(end+1);
dp[begin] = nums[begin];
dp[begin+1] = max(nums[begin],nums[begin+1]);
for(int i=begin+2; i<=end; i++)
dp[i] = max(dp[i-1], dp[i-2]+nums[i]);
return dp[end];
}
};
打家劫舍III
题目链接:力扣
一开始拿到题目的时候,先层序遍历,算出每层的总和,再套第一题的思路。
后来发现这样子是不对的,比如用例 [2,1,3,null,4]
其实这是一道树形dp的题目。
- 确定递归函数的参数和返回值
这里要求得到一个节点 偷与不偷的两个状态所得到的金钱,
那么返回值就是一个长度为2的数组。
则递归函数的定义如下,参数为当前节点
vector<int> robTree(TreeNode* cur)
其实这里的返回数组就是dp数组。
dp数组(dp table)以及下标的含义:
下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
本题dp数组就是一个长度为2的数组!
在递归的过程中,系统栈会保存每一层递归的参数。即标记树中每个节点的状态。
2. 确定终止条件
在遍历的过程中,如果遇到空节点的话,无论偷还是不偷都是0,所以返回
if (cur == NULL) return vector<int>{0, 0};
也相当于dp数组的初始化。
3.确定遍历顺序
首先明确的是使用后序遍历。 因为要通过递归函数的返回值来做下一步计算。
通过递归左节点,得到左节点偷与不偷的金钱。
通过递归右节点,得到右节点偷与不偷的金钱。
4.确定单层递归的逻辑
如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0];
如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,
所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);
最后当前节点的状态就是{val2, val1};
即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}
class Solution {
public:
int rob(TreeNode* root) {
vector<int> result = robTree(root);
return max(result[0], result[1]);
}
vector<int> robTree(TreeNode* cur) {
if (cur == NULL)
return vector<int>{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};
}
};