题目链接:https://leetcode.cn/problems/house-robber/
简单来说就是相邻物品不能同时取。
我们使用 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{取i−2:放弃i−2:choose[i−2]+nums[i]ignore[i−2]+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{取i−1:取i−2:choose[i−1]choose[i−2]
代码如下
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:不取i:dp[i−2]+nums[i]dp[i−1]
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];
}
};
选中第 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:不取i:dp[i−3]+nums[i]dp[i−1]
初始化时,保证 i=1 和 i=2 的时候结果是对的。
可以偷连续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:_ _ _ 0:dp[i−3]+nums[i−1]+nums[i]dp[i−3]+nums[i−2]+nums[i]dp[i−1]
初始化时,保证 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 1:ignore[i−2]+nums[i−1]+nums[i]ignore[i−1]+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 0:choose[i−1]ignore[i−1]
============================================================
上面的方法对于数据全为正整数时没有问题,但是如果有负数,很容易出现连续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 1:ignore[i−2]+nums[i−1]+nums[i]ignore[i−1]+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 0:1 0 0:choose[i−1]chosse[i−2]
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]);
升级题目,不能偷连续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[k−1][i−1]+nums[i]dp[k][i−1]