打家劫舍
顺序偷盗:
- 我考虑的是偷到 i 家,钱的最大值;我固定了 i 次的必须偷到,那上一次投就是i-2或者i-3家,因为i-4家可能往后再偷i-2家。
class Solution {
public:
int rob(vector<int>& nums) {
int pre1sum=0,pre2sum=0,pre3sum=0;
int n =nums.size(),sum=0;
for(int i=0;i<n;i++){
int temp=max(pre2sum,pre1sum) + nums[i];
pre1sum = pre2sum;
pre2sum = pre3sum;
pre3sum = temp;
}
return max(pre3sum,pre2sum);
}
};
- 简单的考虑是路过i家,钱的最大值,这样sum结果就是最后一家的值;两种情况,num[i]+f(i-2)或者f(2)
dp的方向要考虑重点是存储结果的位置
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.empty()) return 0;
int n =nums.size();
if(n == 1) return nums[0];
int pre1sum=nums[0],pre2sum=max(nums[0],nums[1]);
for(int i=2;i<n;i++){
int temp=max(pre2sum,pre1sum + nums[i]);
pre1sum = pre2sum;
pre2sum = temp;
}
return pre2sum;
}
};
循环盗窃
- 仔细考虑,其实只有一种区别,在0位置取了之后,在end-1位置不能取了。分类讨论,两种情况,取0位置和不取0位置dp。
之前一直考虑把取到了0位置的所有分支都标记上,用pair表示sum,但是dp中交换次数很多,而且dp考虑单独特定情况不容易,最后还是启动两次。
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.empty()) return 0;
int n =nums.size();
if(n < 4) return *std::max_element(nums.begin(),nums.end());
return max(helper(nums,0,n-1),helper(nums,1,n));
}
int helper(vector<int>& nums,int start,int end){
int pre1sum=nums[start],pre2sum=max(nums[start],nums[start+1]);
for(int i=start+2;i<end;i++){
int temp=max(pre2sum,pre1sum + nums[i]);
pre1sum = pre2sum;
pre2sum = temp;
}
return pre2sum;
}
};
二叉树盗窃
- 考虑dp两种想法
一种是自顶向下,看每步之后的操作,比如下一步抢或者不抢,然后取Max,经常用到递归函数。但是每次到这一步的前置情况很多,存在很多重叠子问题,需要用备忘录剪枝
一种是自底向上,只考虑单个状态的最大值情况,考虑什么情况能到这一步,更接近结果 - 前面两都是用更难想但是更快的自底向上,树形不好想前置情况(因为树不好找父节点),我先考虑下一步的dp,其实更像带备忘录的纯递归方法
class Solution {
public:
int rob(TreeNode* root) {
if(root == nullptr) return 0;
if(memo.count(root)) return memo[root];
int do_it =root->val +( root->left==nullptr?0:rob(root->left->left)+rob(root->left->right))
+( root->right==nullptr?0:rob(root->right->left)+rob(root->right->right));
int not_do =rob(root->left)+rob(root->right);
int res = max(do_it,not_do);
memo[root] =res;
return res;
}
private:
unordered_map<TreeNode*,int> memo;
};