参考
代码随想录
题目一:LeetCode 198.打家劫舍
- 确定dp数组下标及其含义
dp[i]:考虑下标i(包括i)以内的房屋,可以偷窃的最大金额为dp[i]。 - 确定递推公式
- 如果当前的第i个房间不偷,那么dp[i] = dp[i-1].
- 如果当前的第i个房间要偷,那么只能不偷第i-1个房间,所以dp[i] = dp[i-2] + nums[i].
最终要得到最大金额,因此dp[i] = max(dp[i-1],dp[i-2]+nums[i]).
- 初始化dp数组
dp[0] = nums[0];
dp[1] = max(nums[0],nums[1]);
- 确定遍历顺序
dp[i]要由dp[i-1]和dp[i-2]来确定,因此要从前往后遍历。 - 举例推导dp数组
完整的代码实现如下:
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() == 1) return nums[0];
vector<int> dp(nums.size(), 0);
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.back();
}
};
题目二:LeetCode 213.打家劫舍II
这个题在“198.打家劫舍”的基础上加入了环,如果没有环,每间房间只需要考虑上一间房间是否偷过,加入环之后,“最后”一间房间还要考虑第一间房间是否偷过。但可以将问题转化为上一个题,因为开头和末尾房间只能偷一间,因此分为两种情况,一种是不包含末尾房间,一种是不包含开头房间,最后的结果是这两种的最大值。这两种情况和上一个题完全一样。
代码实现如下:
class Solution {
public:
int robRange(vector<int>& nums, int start, int end)
{
if(start == end) return nums[start];
vector<int> dp(end - start + 1);
dp[0] = nums[start];
dp[1] = max(nums[start],nums[start + 1]);
for(int i = 2; i <= end - start; i++)
dp[i] = max(dp[i-1],dp[i-2] + nums[start+i]);
return dp.back();
}
int rob(vector<int>& nums) {
if(nums.size() == 1) return nums[0];
int val1 = robRange(nums,0,nums.size()-2);
int val2 = robRange(nums,1,nums.size()-1);
return max(val1,val2);
}
};
题目三:LeetCode 337.打家劫舍III
- 确定递归函数的参数和返回值
这里我们要求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组。其实这里的返回数组就是dp数组。
所以dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
vector<int> robTree(TreeNode* cur);
- 确定终止条件
在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回。这里相当于dp数组的初始化。
if (cur == NULL) return vector<int>{0, 0};
- 确定遍历顺序
首先明确的是使用后序遍历。 因为通过递归函数的返回值来做下一步计算。
通过递归左节点,得到左节点偷与不偷的金钱。
通过递归右节点,得到右节点偷与不偷的金钱。
// 下标0:不偷,下标1:偷
vector<int> left = robTree(cur->left); // 左
vector<int> right = robTree(cur->right); // 右
- 确定单层递归逻辑
- 如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0]; (如果对下标含义不理解就在回顾一下dp数组的含义)
- 如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);
最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}
// 偷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};
- 举例推导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);
cout << result[0] << " " << result[1] << endl;
return max(result[0], result[1]);
}
// 长度为2的数组,0:不偷,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};
}
};