House Robber (小偷问题):
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
动态规划的的四个解题步骤是:
- 定义子问题
- 写出子问题的递推关系
- 确定 DP 数组的计算顺序
- 空间优化
步骤一:定义子问题
子问题是和原问题相似,但规模较小的问题。小偷问题,原问题是 “从全部房子中能偷到的最大金额”,将问题的规模缩小,子问题就是 “从 k个房子中能偷到的最大金额 ”,用 f(k)表示。
步骤二:写出子问题的递推关系
这一步是求解动态规划问题最关键的一步。
假设一共有 n个房子,每个房子的金额分别是,
,
…
,子问题 f(k) 表示从前 k 个房子中能偷到的最大金额。那么,偷 k个房子有两种偷法:
k 个房子中最后一个房子是 。如果不偷这个房子,那么问题就变成在前 k−1个房子中偷到最大的金额,也就是子问题 f(k−1)。如果偷这个房子,那么前一个房子
显然不能偷,其他房子不受影响。那么问题就变成在前 k−2k-2k−2 个房子中偷到的最大的金额。两种情况中,选择金额较大的一种结果。
在写递推关系的时候,要注意写上 k=0k=0k=0 和 k=1k=1k=1 的基本情况:
当 k=0时,没有房子,所以 f(0)=0。
当 k=1时,只有一个房子,偷这个房子即可,所以 f(1)=。
步骤三:确定 DP 数组的计算顺序
DP 数组也可以叫”子问题数组”,因为 DP 数组中的每一个元素都对应一个子问题。
那么,只要搞清楚了子问题的计算顺序,就可以确定 DP 数组的计算顺序。对于小偷问题,我们分析子问题的依赖关系,发现每个 f(k) 依赖 f(k−1)和 f(k−2)。也就是说,dp[k] 依赖 dp[k-1] 和 dp[k-2],如下图所示。
那么,既然 DP 数组中的依赖关系都是向右指的,DP 数组的计算顺序就是从左向右。这样我们可以保证,计算一个子问题的时候,它所依赖的那些子问题已经计算出来了。
题解代码如下:
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0) {
return 0;
}
int n = nums.size();
vector<int>dp(n + 1, 0);
dp[0] = 0;
dp[1] = nums[0];
for (int k = 2;k <= n;k++)
{
dp[k] = max(dp[k - 1], dp[k - 2] + nums[k - 1]);
}
return dp[n];
}
};