LeetCode198.打家劫舍
1.思路
当前房屋是否要偷取决于前一个房屋和前两个房屋的情况,所以当前房屋的状态收到前面房屋状态的依赖。
动态规划五部曲:
1. dp[i]:从当前房屋 i 及以前的房屋中可以偷窃的最大金额;
2. 如果偷第 i 个房间,那么就找到 i-2 以内的房间能偷窃的最大金额,再加上当前房间的价值,即 dp[i - 2] + nums[i];如果不偷第 i 个房间,那么当前 dp[i] 就取决于 i-1 以内的房间的最大金额,即 dp[i - 1],然后取这两种情况中的最大值。
所以递推公式:dp[i] = max(dp[i - 1],dp[i - 2] + nums[i]);
3. 由递推公式可以看出,递推公式的基础就是 dp[0],dp[1],所以根据 dp[i] 的定义,dp[0] = nums[0],dp[1] = max(nums[0],nums[1]);
4. dp[i] 依赖于 dp[i - 1] 和 dp[i - 2],所以是从前往后遍历;
5. nums = [2,7,9,3]

class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()<2) 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[nums.size()-1];
}
};
2.复杂度分析
时间复杂度:O(n)
空间复杂度:O(n)
3.思考
这道题就不是背包问题了,我一开始还想着用背包去解,还用了两层 for 循环。不过思路大体是一致的,还是五部曲。要解决这道题,首先要想清楚 dp[i] 的定义,其次就是当前 dp[i] 能通过哪些情况推出,然后就是根据递推公式来确定遍历顺序。
4.Reference:198.打家劫舍 | 代码随想录
LeetCode213.打家劫舍II
1.思路
这道题和 打家劫舍1 不同,这里连成了环,所以我们要考虑首元素和尾元素的取舍。
第一种:考虑包含首元素,不包含尾元素

第二种:考虑包含尾元素,不包含首元素

这里不需要考虑只取中间,不取两边的情况,因为情况一和情况二已经包含了这一情况。
其余部分和打家劫舍1一样,这里就只用考虑两种情况,分别求解最大值,然后取最大值即可。
class Solution {
public:
int maxValue(vector<int>& nums,int l,int r){
if(l>=r) return nums[l];
vector<int>dp(nums.size(),0);
dp[l]=nums[l];
dp[l+1]=max(nums[l],nums[l+1]);
for(int i=l+2;i<=r;i++){
dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[r];
}
int rob(vector<int>& nums) {
if(nums.size()==0) return 0;
if(nums.size()<2) return nums[0];
int n1 = maxValue(nums,0,nums.size()-2); // 情况一
int n2 = maxValue(nums,1,nums.size()-1); // 情况二
return max(n1,n2);
}
};
2.复杂度分析
时间复杂度:O(n)
空间复杂度:O(n)
3.思考
把线性关系改成环形关系,就需要考虑头和尾的取舍情况,不能既顾头又顾尾,所以这里要考虑带头不带尾,带尾不带头的情况,而不带头不带尾的情况则不用考虑,因为已经包含在上述两种情况中。
4.Reference:213.打家劫舍II | 代码随想录
LeetCode337.337.打家劫舍III
1.思路
这里定义一个长度为 2 的数组 dp[2],dp[0] 记录不偷当前节点所得的最大金额,dp[1] 记录偷当前节点所得到的最大金额。
1. 要求每一个节点偷与不偷的两种状态,所以要返回数组,即相当于返回每一个节点的 dp 数组;
2. 在遍历的过程中,如果遇到空节点的话,无论偷还是不偷都是0,所以就返回 {0,0},相当于 dp 数组的初始化;
3. 后序遍历,先得到左节点偷与不偷的最大金额,再得到右节点偷与不偷的最大金额,最后处理当前节点偷与不偷的情况;
4. 如果偷当前节点,那么左右孩子都不能偷,所以 val1 = root->val + leftdp[0] + rightdp[0];如果不偷当前节点,那么就分别选取左孩子和有孩子两种情况中的最大金额,然后作和,即 val0 = max(leftdp[0],leftdp[1]) + max(rightdp[0],rightdp[1]);然后返回当前节点的状态 {val0,val1};
5. 举例 dp 数组,最后头结点就是取下标 0 和 下标 1 的最大值就是偷得的最大金钱。

class Solution {
public:
// 长度为2的数组,0:不偷,1:偷
vector<int> robtree(TreeNode* root){
if(root==NULL) return {0,0};
vector<int>leftdp = robtree(root->left);
vector<int>rightdp = robtree(root->right);
// 不偷当前节点,则偷左右孩子的最大值
int val0 = max(leftdp[0],leftdp[1])+max(rightdp[0],rightdp[1]);
// 偷当前节点,则不能偷左右孩子
int val1 = root->val+leftdp[0]+rightdp[0];
return {val0,val1};
}
int rob(TreeNode* root) {
vector<int>res = robtree(root);
return max(res[0],res[1]);
}
};
2.复杂度分析
时间复杂度:O(n) - 每个节点只遍历了一次
空间复杂度:O(log n)
3.思考
这道题是树形 dp 的入门题目,也就是要在树上进行递推公式的推导,但是平时习惯了在一维数组上推导公式,一下子换成了树就没什么思路了。

1133

被折叠的 条评论
为什么被折叠?



