一、题目[LeetCode-198]
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
- 1 <= nums.length <= 100
- 0 <= nums[i] <= 400
二、思路
动态规划
由题意得,即求数组nums的最大元素和(其中取出的元素不能相邻),恰好与《day36 最大子序和》相反,该题要求数组中最大的连续的元素和。设dp[i][0]表示经过第i家(i从0计)后选择不偷该家的钱,即遍历到数组第i个元素后选择不加上该元素;dp[i][1]则表示遍历到数组第i个元素后选择加上该元素。
对于dp[i][0],要么是第i-1家没偷(即dp[i-1][0]),要么是第i-1家偷了(即dp[i-1][1]),直接取二者最大者即可;对于dp[i][1],只能是第i-1家没偷,然后才能偷第i家。
因此得出动态规划转移方程:
dp[i][0] = max(dp[i-1][0], dp[i-1][1])
dp[i][1] = dp[i-1][0] + nums[i]
初始条件:dp[0][0] = 0, dp[0][1] = nums[0].
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> dp(n);
for(int i = 0; i < n; i++)
dp[i] = vector<int>(2);//声明动态规划数组
dp[0][0] = 0;
dp[0][1] = nums[0];//初始化动态规划数组初始条件
for(int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
dp[i][1] = dp[i-1][0] + nums[i];
}
return max(dp[n-1][0], dp[n-1][1]);
}
};
动态规划优化(滚动数组)
同理,可以大大优化,不使用数组的实现方式,而仅仅使用两个变量dp0和dp1进行n-1次迭代即可。
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
int dp0 = 0;
int dp1 = nums[0];//动态规划初始条件
for(int i = 1; i < n; i++)
{
int newDp0 = max(dp0, dp1);
dp1 = dp0 + nums[i];
dp0 = newDp0;
}
return max(dp0, dp1);
}
};
三、官方题解(来源:力扣(LeetCode))
方法:动态规划(另一种动态规划思路)
首先考虑最简单的情况。如果只有一间房屋,则偷窃该房屋,可以偷窃到最高总金额。如果只有两间房屋,则由于两间房屋相邻,不能同时偷窃,只能偷窃其中的一间房屋,因此选择其中金额较高的房屋进行偷窃,可以偷窃到最高总金额。
如果房屋数量大于两间,应该如何计算能够偷窃到的最高总金额呢?对于第 k (k>2) 间房屋,有两个选项:
- 偷窃第 k 间房屋,那么就不能偷窃第 k−1 间房屋,偷窃总金额为前 k−2 间房屋的最高总金额与第 k 间房屋的金额之和。
- 不偷窃第 k 间房屋,偷窃总金额为前 k−1 间房屋的最高总金额。
在两个选项中选择偷窃总金额较大的选项,该选项对应的偷窃总金额即为前 k 间房屋能偷窃到的最高总金额。
用 dp[i] 表示前 i 间房屋能偷窃到的最高总金额,那么就有如下的状态转移方程:
dp[i] = max(dp[i-2]+nums[i], dp[i-1])
边界条件为:dp[0] = nums[0], dp[1] = max(nums[0], nums[1])
最终答案即为dp[n-1],n为数组长度
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.empty()) {
return 0;
}
int size = nums.size();
if (size == 1) {
return nums[0];
}
vector<int> dp = vector<int>(size, 0);
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < size; i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[size - 1];
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/house-robber/solution/da-jia-jie-she-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
上述方法使用了数组存储结果。考虑到每间房屋的最高总金额只和该房屋的前两间房屋的最高总金额相关,因此可以使用滚动数组,在每个时刻只需要存储前两间房屋的最高总金额。
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.empty()) {
return 0;
}
int size = nums.size();
if (size == 1) {
return nums[0];
}
int first = nums[0], second = max(nums[0], nums[1]);
for (int i = 2; i < size; i++) {
int temp = second;
second = max(first + nums[i], second);
first = temp;
}
return second;
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/house-robber/solution/da-jia-jie-she-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
复杂度分析
- 时间复杂度:O(n),其中 n 是数组长度。只需要对数组遍历一次。
- 空间复杂度:O(1)。使用滚动数组,可以只存储前两间房屋的最高总金额,而不需要存储整个数组的结果,因此空间复杂度是 O(1)。
四、学习心得
①对于本题动态规划状态转移方程的两种思路:
(1)dp[i][0]表示经过第i家(i从0计)后选择不偷该家的钱,;dp[i][1]表示经过第i家后选择不偷该家的钱。
dp[i][0] = max(dp[i-1][0], dp[i-1][1])
dp[i][1] = dp[i-1][0] + nums[i]
(2)dp[i] 表示前 i 间房屋能偷窃到的最高总金额
dp[i] = max(dp[i-2]+nums[i], dp[i-1])
②数组数据结构问题涉及到多种情况并且存在先后的递推关系时可以首先考虑动态规划
③滚动数组的动态规划优化方案可以将空间复杂度从O(n)优化至O(1)