参考
代码随想录
题目一: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};
    }
};



















