算法学习day47

算法学习day47

  • 1.力扣 198.打家劫舍
    • 1.1 分析
    • 1.2 代码
  • 2.力扣213.打家劫舍II
    • 2.1 分析
    • 2.2 代码
  • 3.力扣337.打家劫舍III
    • 3.1 分析
    • 3.2 代码
  • 4.参考资料

1.力扣 198.打家劫舍

1.1 分析

1.确定dp数组以及下标的含义

dp[i] : 考虑下标i(包括i)以内的房屋,最多可以偷窃的金额数为dp[i]

2.确定递推公式

如果偷第i房间,dp[i] = dp[ i -2 ] + nums[i]。i - 1 房间不偷,下标i -2 (包括i - 2)以内的房间最多可以偷的金额为dp[i -2]加上第i房间偷的钱。

如果不偷第i房间,此时dp[i] = dp[ i -1]。

递推公式: dp[i] = max(dp[ i - 2]+ nums[i], dp[i - 1]);

3.dp数组如何初始化

由递推公式: dp[i] = max(dp[ i - 2]+ nums[i], dp[i - 1]); 递推公式的基础是dp[0]、dp[1]。

由dp的含义可知dp[0] = nums[0], dp[1] = max(nums[0] + nums[1])

初始化如下:

vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0],nums[1]);

4.确定遍历顺序

dp[i] 是由dp[i-1]和dp[i-2]推导出来的,那么一定是从前到后遍历。

for(int i = 2; i < nums.size() ; i++){
    dp[i] = max(dp[i-2] + nums[i], dp[i -1]);
}

5.举例推导dp数组
算法学习day47_第1张图片

1.2 代码

class Solution {
public:
    int rob(vector<int>& nums) {                
        if(nums.size() == 0) return 0;                      // 当数组为空时,直接返回 0
        if(nums.size() == 1) return nums[0];       // 当数组只有一个元素时,直接返回该元素的值
        vector<int> dp(nums.size());                     // 定义 dp 数组,长度为 nums 数组的长度
        dp[0] = nums[0];                                             // 初始化 dp[0] 为 nums[0]
        dp[1] = max(nums[0], nums[1]);             // 初始化 dp[1],选择 nums[0] 和 nums[1] 中较大的一个
        for(int i = 2 ; i < nums.size(); i++){            // 遍历数组
            dp[i] = max(dp[i -2]+nums[i],dp[i-1]); // 状态转移方程
        }
        return dp[nums.size() - 1];                        // 返回 dp 数组最后一个元素的值,即为所求
    }
};

2.力扣213.打家劫舍II

2.1 分析

1.考虑不包含首尾元素
算法学习day47_第2张图片
2.考虑包含首元素,不包含尾元素
算法学习day47_第3张图片
3.考虑包含尾元素,不包含首元素
算法学习day47_第4张图片

2.2 代码

class Solution {
public:
    int rob(vector<int>& nums) {
        // 如果数组为空,直接返回0
        if(nums.size() == 0) return 0;
        // 如果数组长度为1,直接返回该数值
        if(nums.size() == 1) return nums[0];
        // 情况二:偷取[0, n-2]范围内的房屋
        int result1 = robRange(nums,0,nums.size() - 2);
        // 情况三:偷取[1, n-1]范围内的房屋
        int result2 = robRange(nums,1,nums.size() - 1);
        // 取两种情况的最大值
        return max(result1,result2);
    }
    // 198题:打家劫舍问题
    int robRange(vector<int>& nums, int start, int end){
        // 如果范围内只有一个房屋,直接返回该数值
        if(end == start) return nums[start];
        // 初始化dp数组,dp[i]表示偷取[0,i]范围内的房屋所能获得的最大收益
        vector<int> dp(nums.size());
        dp[start] = nums[start];
        // 计算dp[1]
        dp[start+1] = max(nums[start],nums[start+1]);
        // 遍历数组,计算dp[i]
        for(int i = start+2; i<=end; i++){
            // 当偷取第i个房屋时,偷取范围只能是[0, i-2],因此dp[i] = dp[i-2] + nums[i]
            // 当不偷取第i个房屋时,偷取范围是[0, i-1],因此dp[i] = dp[i-1]
            dp[i] = max(dp[i-2] + nums[i], dp[i-1]);
        }
        // 返回偷取[start, end]范围内的房屋所能获得的最大收益
        return dp[end];
    }
};

3.力扣337.打家劫舍III

3.1 分析

1.确定递归函数的参数和返回值

一个节点,偷or不偷。返回值为长度为2的数组

vector<int> robTree(TreeNode* cur){}

dp数组以及下标的含义:下标0表示不偷该节点获得的最大钱数,1表示偷该节点获得的最大钱数。

本题的dp数组是一个长度为2的数组。

2.确定终止条件

遍历过程中遇到空节点,返回。

if(cur == NULL) return vector<int>(0, 0);

3.确定遍历顺序

后序遍历。

// 下标0:不偷        下标1:偷
vector<int> left = robTree(cur->left);// 左
vector<int>right = robTree(cur->right);// 右
// 中

4.确定单层递归的逻辑

当前节点偷,由题意。左右孩子不能偷。val1= cur->val + left[0] + right[0];

如果不偷当前节点,左右孩子可偷。val2 = max(left[0],left[1]) + max(right[0],right[1]);

最后当前节点状态{val2,val1};{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}

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};

5.举例推导dp数组
算法学习day47_第5张图片

3.2 代码

class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> result = robTree(root);  // 获取最终结果
        return max(result[0], result[1]);    // 返回最大值
    }
    
    // 辅助函数,返回一个长度为2的数组,表示在当前节点为根节点时偷或不偷的最大值
    vector<int> robTree(TreeNode* cur) {
        if (cur == NULL) return vector<int>{0, 0};  // 空节点返回0
        vector<int> left = robTree(cur->left);      // 递归处理左子树
        vector<int> right = robTree(cur->right);    // 递归处理右子树
        int val1 = cur->val + left[0] + right[0];   // 偷当前节点,左右子树都不能偷
        int val2 = max(left[0], left[1]) + max(right[0], right[1]); // 不偷当前节点,左右子树可偷可不偷
        return {val2, val1};  // 返回结果
    }
};

4.参考资料

[代码随想录]

你可能感兴趣的:(算法,数据结构,动态规划,c++,leetcode)