[leetcode 198] 打家劫舍

题目

题目链接:https://leetcode.cn/problems/house-robber/

[leetcode 198] 打家劫舍_第1张图片

解法

简单来说就是相邻物品不能同时取。

方法一

我们使用 choose[i] 表示取(必须取)第 i 件所能得到的最大值,ignore[i] 表示放弃(必须放弃)第 i 件所能得到的最大值。

ignore[i] 可能会比 choose[i] 大,因为 choose[i] 必须取第 i 件物品,因此无法取第 i-1 件物品,但第 i-1 件物品可能价值很大。

状态转移方程如下

必须取 i { 取 i − 2 : c h o o s e [ i − 2 ] + n u m s [ i ] 放弃 i − 2 : i g n o r e [ i − 2 ] + n u m s [ i ] 必须取i\left\{ \begin{aligned} &取i-2:&choose[i-2]+nums[i]\\ &放弃i-2:&ignore[i-2]+nums[i] \\ \end{aligned} \right. 必须取i{i2放弃i2choose[i2]+nums[i]ignore[i2]+nums[i]

必须放弃 i { 取 i − 1 : c h o o s e [ i − 1 ] 取 i − 2 : c h o o s e [ i − 2 ] 必须放弃i\left\{ \begin{aligned} 取i-1:&choose[i-1]\\ 取i-2:&choose[i-2] \\ \end{aligned} \right. 必须放弃i{i1i2choose[i1]choose[i2]

代码如下

class Solution {
public:
    int rob(vector<int>& nums) {
        int len = nums.size();
        if (len == 1) {
            return nums[0];
        }
        vector<int> choose(len);
        vector<int> ignore(len);
        int ans = 0;
        choose[0] = nums[0]; choose[1] = nums[1];
        ignore[0] = 0;       ignore[1] = nums[0];
        for (int i = 0; i < 2; i++) {
            ans = max(choose[i], ignore[i]);
        }
        for (int i = 2; i < len; i++) {
            choose[i] = max(choose[i-2], ignore[i-2]) + nums[i];
            ignore[i] = max(choose[i-1], choose[i-2]);
            ans = max(choose[i], ignore[i]);
        }
        return ans;
    }
};

方法二

其实可以把上面提到的两种状态合并,即我们只用一个数组dp,dp[i] 表示到第 i 件物品所能取得的最大值,第 i 件物品我们可取可放弃。

d p [ i ] { 取 i : d p [ i − 2 ] + n u m s [ i ] 不取 i : d p [ i − 1 ] dp[i]\left\{ \begin{aligned} &取i:&dp[i-2]+nums[i]\\ &不取i:&dp[i-1] \\ \end{aligned} \right. dp[i]{i不取idp[i2]+nums[i]dp[i1]

i=1 时,可以把 dp[-1] 当作 0 处理,保证 i=1 的时候结果是对的。

代码如下

class Solution {
public:
    int rob(vector<int>& nums) {
        int len = nums.size();
        if (len == 1) {
            return nums[0];
        }
        vector<int> dp(len);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        for (int i = 2; i < len; i++) {
            dp[i] = max(dp[i-2]+nums[i], dp[i-1]);
        }
        return dp[len-1];
    }
};

题目升级(美团23春)

选中第 i 个用户后,i-2,i-1,i+1,i+2 不能选,问能偷的最大价值?

我们只用一个数组dp,dp[i] 表示到第 i 件物品所能取得的最大值,第 i 件物品我们可取可放弃。

d p [ i ] { 取 i : d p [ i − 3 ] + n u m s [ i ] 不取 i : d p [ i − 1 ] dp[i]\left\{ \begin{aligned} &取i:&dp[i-3]+nums[i]\\ &不取i:&dp[i-1] \\ \end{aligned} \right. dp[i]{i不取idp[i3]+nums[i]dp[i1]

初始化时,保证 i=1 和 i=2 的时候结果是对的。

题目升级(蚂蚁23春)

可以偷连续2个住户,但是不能偷连续3个住户,但连续3个住户中至少偷一个。问能偷的最大价值?

我们只用一个数组dp,dp[i] 表示到第 i 件物品所能取得的最大值,第 i 件物品我们可取可放弃。

d p [ i ] { _   0   1   1 : d p [ i − 3 ] + n u m s [ i − 1 ] + n u m s [ i ] _   1   0   1 : d p [ i − 3 ] + n u m s [ i − 2 ] + n u m s [ i ] _   _   _   0 : d p [ i − 1 ] dp[i]\left\{ \begin{aligned} &\_\ 0\ 1\ 1:&dp[i-3]+nums[i-1]+nums[i]\\ &\_\ 1\ 0\ 1:&dp[i-3]+nums[i-2]+nums[i]\\ &\_\ \_\ \_\ 0:&dp[i-1] \\ \end{aligned} \right. dp[i] _ 0 1 1_ 1 0 1_ _ _ 0dp[i3]+nums[i1]+nums[i]dp[i3]+nums[i2]+nums[i]dp[i1]

初始化时,保证 i=1 和 i=2 的时候结果是对的。

============================================================
上面的解法是错误的,因为 dp[i-3] 到底取没取是不确定的!第一种情况导致可能出现 0 1 1,第二种情况可能出现 1 0 1,两种情况组合起来就是 0 1 1 1 0 1。

为了把取没取的情况去的情况确定下来,必须要用两个数组 choose 和 ignore

状态转移方程如下

c h o o s e [ i ] { 0   1   1 : i g n o r e [ i − 2 ] + n u m s [ i − 1 ] + n u m s [ i ] _   0   1 : i g n o r e [ i − 1 ] + n u m s [ i ] choose[i]\left\{ \begin{aligned} &0\ 1\ 1:&ignore[i-2]+nums[i-1]+nums[i]\\ &\_\ 0\ 1:&ignore[i-1]+nums[i] \\ \end{aligned} \right. choose[i]{0 1 1_ 0 1ignore[i2]+nums[i1]+nums[i]ignore[i1]+nums[i]

i g n o r e [ i ] { _   1   0 : c h o o s e [ i − 1 ] _   0   0 : i g n o r e [ i − 1 ] ignore[i]\left\{ \begin{aligned} \_\ 1\ 0:&choose[i-1]\\ \_\ 0\ 0:&ignore[i-1] \\ \end{aligned} \right. ignore[i]{_ 1 0_ 0 0choose[i1]ignore[i1]

============================================================

上面的方法对于数据全为正整数时没有问题,但是如果有负数,很容易出现连续3个住户都不偷的情况。所以ignore的转移方程应该修改。

c h o o s e [ i ] { 0   1   1 : i g n o r e [ i − 2 ] + n u m s [ i − 1 ] + n u m s [ i ] _   0   1 : i g n o r e [ i − 1 ] + n u m s [ i ] choose[i]\left\{ \begin{aligned} &0\ 1\ 1:&ignore[i-2]+nums[i-1]+nums[i]\\ &\_\ 0\ 1:&ignore[i-1]+nums[i] \\ \end{aligned} \right. choose[i]{0 1 1_ 0 1ignore[i2]+nums[i1]+nums[i]ignore[i1]+nums[i]

i g n o r e [ i ] { _   1   0 : c h o o s e [ i − 1 ] 1   0   0 : c h o s s e [ i − 2 ] ignore[i]\left\{ \begin{aligned} \_\ 1\ 0:&choose[i-1]\\ 1\ 0\ 0:&chosse[i-2] \\ \end{aligned} \right. ignore[i]{_ 1 01 0 0choose[i1]chosse[i2]

i=0和i=1初始化一定要按照转移方程来!越界的话直接视为零。

ignore[0] = 0; ignore[1] = max(0 ,choose[0]);
choose[0] = nums[0], choose[1] = max(nums[0]+nums[1], ignore[0]+nums[1]);

题目升级(美团23春)

升级题目,不能偷连续2个住户,但有 k 次违反该规则的机会,问能偷的最大价值?

我们只用一个二维数组dp,dp[k][i] 表示有 k 次机会,到第 i 件物品所能取得的最大值,第 i 件物品我们可取可放弃,这 k 次机会可用可不用。

d p [ k ] [ i ] { 对第 i 个物品使用机会: d p [ k − 1 ] [ i − 1 ] + n u m s [ i ] 对第 i 个物品不使用机会: d p [ k ] [ i − 1 ] dp[k][i]\left\{ \begin{aligned} &对第 i 个物品使用机会:&dp[k-1][i-1]+nums[i]\\ &对第 i 个物品不使用机会:&dp[k][i-1]\\ \end{aligned} \right. dp[k][i]{对第i个物品使用机会:对第i个物品不使用机会:dp[k1][i1]+nums[i]dp[k][i1]

你可能感兴趣的:(leetcode,算法,动态规划)