打家劫舍
- 198. 打家劫舍
- 1. dp数组以及下标名义
- 2. 递归公式
- 3. dp数组如何初始化
- 4. 遍历顺序
- 5. 代码
- 213. 打家劫舍 II:房间首尾相连
- 1. dp数组以及下标名义
- 2. 递归公式
- 3. dp数组如何初始化
- 4. 遍历顺序
- 5. 代码
- 337. 打家劫舍 III:
- 1. 递归,后序遍历
- 动态规划:树形dp的入门题目
- 1.确定递归函数的参数和返回值
- 2. 确定终止条件
- 3. 确定遍历顺序
- 4. 确定单层递归的逻辑
- 5. 代码
198. 打家劫舍
如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
1. dp数组以及下标名义
dp[i] : 考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]
2. 递归公式
dp[1] = 2;
dp[2]=7;
dp[3]=2+9;
偷第i间:dp[i] = dp[i - 2] +nums[i]
不偷第i间:dp[i] = dp[i - 1]
dp[i]=max(dp[i - 1],dp[i - 2] +nums[i])
3. dp数组如何初始化
dp[0]=0
4. 遍历顺序
5. 代码
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() == 0)return 0;
if(nums.size() == 1)return nums[0];
vector<int>dp(nums.size() + 2, 0);
dp[1] = nums[0];//从下标1开始算1号房间,不要0下标
// dp[2] = max(nums[0], nums[1]);
for (int i = 2; i <= nums.size(); i++) {
dp[i] = max(dp[i - 2] + nums[i - 1], dp[i - 1]);
}
return dp[nums.size()];
}
};
213. 打家劫舍 II:房间首尾相连
对于一个数组,成环的话主要有如下三种情况:
- 情况一:考虑不包含首尾元素
2.考虑包含首元素,不包含尾元素
- 情况三:考虑包含尾元素,不包含首元素
只需要考虑情况二和情况三,因为这两种情况包含了情况一
1. dp数组以及下标名义
dp[i] : 考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]
2. 递归公式
dp[i]=max(dp[i - 1],dp[i - 2] +nums[i])
3. dp数组如何初始化
dp[0]=0
4. 遍历顺序
5. 代码
class Solution {
public:
int getrob(vector<int>& nums, int start, int end) {
vector<int >dp(nums.size() + 1, 0);//要用nums.size() + 1
dp[start + 1] = nums[start ];
for(int i = start + 2; i <= end; i++) {
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
}
return dp[end];
}
int rob(vector<int>& nums) {
if(nums.size() == 0) return 0;
if(nums.size() == 1) return nums[0];
if(nums.size() == 2) return max(nums[0], nums[1]);
int ahead = getrob(nums, 0 , nums.size() - 1);
cout<<ahead<<endl;
int backhead = getrob(nums, 1 , nums.size());
cout<<backhead<<endl;
return max(ahead, backhead);
}
};
337. 打家劫舍 III:
1. 递归,后序遍历
/**
* 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:
map<TreeNode* , int>umap;
int rob(TreeNode* root) {
if(root == nullptr)return 0;
if(root->left == nullptr && root->right ==nullptr) return root->val;
if(umap[root]) return umap[root];
//偷父节点
int val1 = root->val;
if(root->left)val1 += rob(root->left->left) + rob(root->left->right);//跳过root->left
if(root->right)val1 += rob(root->right->left) + rob(root->right->right);//跳过root->right
//不偷父节点
int val2 = rob(root->left) + rob(root->right);
umap[root] = max(val1,val2);
return max(val1, val2);
}
};
动态规划:树形dp的入门题目
1.确定递归函数的参数和返回值
这里我们要求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组。
参数为当前节点,代码如下:
vector<int> robTree(TreeNode* cur) {
这里的返回数组就是dp数组
dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
2. 确定终止条件
在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回
if (cur == NULL) return vector<int>{0, 0};相当于dp数组初始化
3. 确定遍历顺序
后序遍历
通过递归左节点,得到左节点偷与不偷的金钱。
通过递归右节点,得到右节点偷与不偷的金钱。
// 下标0:不偷,下标1:偷
vector<int> left = robTree(cur->left); // 左
vector<int> right = robTree(cur->right); // 右
// 中
4. 确定单层递归的逻辑
如果是偷当前节点,那么左右孩子就不能偷
如果不偷当前节点,那么左右孩子就可以偷
至于到底偷不偷一定是选一个最大的
5. 代码
/**
* 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]);
}
长度为2的数组,0:不偷,1:偷
vector<int> robTree(TreeNode* cur) {
if(cur == nullptr) 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];
//不偷,可以偷也可以不偷左右节点,取较大的情况
int val2 = max(left[0], left[1])+ max(right[0], right[1]);
return {val2,val1};//不能反
}
};