代码随想录 11.08 || 动态规划 LeetCode 198.打家劫舍、213.打家劫舍Ⅱ、337.打家劫舍Ⅲ

文章讲述了利用动态规划解决小偷在有防盗系统的房屋中偷窃的问题,通过dp数组递推公式计算在不触动警报的情况下每晚可偷最高金额,涉及不同场景下的策略:单链、环形和树形结构。
摘要由CSDN通过智能技术生成

        今天,开启打家劫舍的一天!!!

198.打家劫舍

        你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

        确定 dp[ j ] 的含义,在第 j 间房间最多能偷到 dp[ j ] 金额;

        确定递推公式,在第 j 间房间中偷取到的金额取决于 j - 1 和 j - 2 房间的状态,如果不偷 j 房间,则 dp[ j ] = dp [ j - 1 ];如果偷 j 房间,则表明 j - 1 房间没偷,dp [ j ] = dp[ j - 2 ] + nums[ j ]。如何决定 偷 or 不偷 的状态呢?取决于 dp [ j - 1 ] 和 dp[ j - 2 ] + nums[ j ] 的大小;

        确定遍历顺序,明显从前往后,j 取决于 j - 1 和 j - 2;

        初始化 dp 数组,当前房间根据前两个房间的状态推导而来,因此 dp[ 0 ] = nums[ 0 ],dp[ 1 ] = nums[ 1 ]。

        在出问题时,打印 dp 数组 debug。

class Solution {
public:
    int rob(vector<int> &nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        
        auto dp = vector<int> (nums.size(), 0);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);

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

        }

        return dp[nums.size() - 1];
    }
};

213.打家劫舍Ⅱ

        本题与上题的不同之处在于,本题的首尾房子之间相连,成环。如果偷第一个房间,则不能偷最后一个房间,反之亦然。成环带来一个问题,无法选择开始点,是从第一间房间开始,还是从最后一间房间开始?如何初始化 dp 数组?

        成环状态下,可分解为两种子问题,包括首房间,不包括尾房间;包括尾房间,不包括首房间。这两种情况的并集就是即包括首,又包括尾。我们将这两种情况分别处理,用我们上一题编写的函数处理,然后选出每一种的最大值即可。

class Solution {
private:
    int robRange(vector<int> &nums, int start, int end) {
        if (start == end) return nums[start];

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

        for (int j = start + 2; j <= end; ++j) {
            dp[j] = max(dp[j - 1], dp[j - 2] + nums[j]);
        }

        return dp[end];
    }
    
public:
    int rob(vector<int> &nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];

        int head = robRange(nums, 0, nums.size() - 2);
        int tail = robRange(nums, 1, nums.size() - 1);

        return max(head, tail);
    }
};

337.打家劫舍Ⅲ

        树形 dp,节点表示房间,相邻的节点不能被偷。如果偷了父节点元素,则左子节点和右子节点元素都不能被偷。本层节点能偷取的最大金额,取决于下一层(树的更深层)能偷取的金额,以此类推,最终到达叶子节点,因此遍历顺序为后序遍历,先收集子节点的信息,然后决定根节点的状态。

        dp 数组定义为具有两个元素的数组,dp [ 0 ] 表示当前节点不偷,dp [1] 表示当前节点偷,则对于某一个二叉树节点, dp[ 1 ] = node->val + left[ 0 ] + right[ 0 ],dp[ 0 ] = max(left[ 0 ],right[ 1 ]) + max(right[ 0 ], right[ 1 ])。当前二叉树节点代表的房间偷与不偷,取决于偷到的金额和不偷的金额谁大,取最大的状态。

class Solution {
private:
    vector<int> robTree(TreeNode *node) {
        if (node == nullptr) return {0, 0};

        vector<int> left = robTree(node->left);
        vector<int> right = robTree(node->right);

        int val1 = node->val + left[0] + right[0];
        int val2 = max(left[0], left[1]) + max(right[0], right[1]);

        return {val2, val1};
    }

public:
    int rob(TreeNode *root) {
        vector<int> dp = robTree(root);
        
        return max(dp[0], dp[1]);
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值