LeetCode 热题 100_打家劫舍(83_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
题解:
解题思路:
思路一(动态规划(一维dp数组)):
1、解决此问题可以分为4个步骤:
① 定义子问题
② 写出子问题的递推公式
③ 确定DP数组的计算顺序和DP数组的初始化
④ 空间优化(可选)
① 子问题:
- 当我们偷第i个房间时,则第i-1房间不能偷,i-2和之前的可以偷
- 当我们不偷第i个房间时,第则i-1和之前的可以偷
- 我们通过比较偷i房间和不偷i房间的金额来判定当前偷的最大金额
- f(i)可由f(i-1)和f(i-2)求解
② 通过分析子问题我们自然的能写出递推公式:
- f(i)=max{f(i-1),f(i-2)+nums[i]},也就是:dp[i]=max{dp[i-1],dp[i-2]+nums[i]}
③ 确定DP数组的计算顺序:
- 递推公式f(i)可由f(i-1)和f(i-2)求解,所以计算顺序为从左到右顺序进行
④ 空间优化
- 可通过滚动数组来记录部分DP数组,来进行空间优化
例 :
nums=[2,7,9,3,1]
当i=0时,只有一个可偷的房间,dp[0]=2。(dp代表当前下标最多能偷的金额)
当i=1时,可偷2,7两间房中的其中一个(7>2),dp[1]=7
当i=2时, 此时7(nums[1])是否可偷取决于9(nums[2])是否被偷。
- 当偷nums[2]时,则nums[1]不能偷,nums[1]之前的可以偷,可偷的金额为9+2(dp[0])=11
- 当不偷nums[2]时,则nums[1]和nums[1]之前的都能偷,可偷金额为7(dp[1])=7
- 则dp[2]=max(11,7)=11
当i=3时,此时9(nums[2])是否可偷取决于3(nums[3])是否被偷。
- 当偷nums[3]时,则nums[2]不能偷,nums[2]之前的可以偷,可偷的金额为3+7(dp[1])=10
- 当不偷nums[3]时,则nums[2]和nums[2]之前的都能偷,可偷金额为11(dp[2])=11
- 则dp[3]=max(10,11)=11
当i=4时,此时3(nums[3])是否可偷取决于1(nums[4])是否被偷。
- 当偷nums[4]时,则nums[3]不能偷,nums[3]之前的可以偷,可偷的金额为1+11(dp[2])=12
- 当不偷nums[4]时,则nums[3]和nums[3]之前的都能偷,可偷金额为11(dp[3])=11
- 则dp[4]=max(12,11)=11
2、我们通过上述过程总结出,我们将nums[i]偷与不偷分为两种情况:
- nums[i]偷则nums[i-1]不能偷,此时可偷的金额最大值取决于i-2及之前位置的可偷金额的最大值+nums[i]。
- nums[i]不偷则nums[i-1]能偷,此时可偷的金额最大值取决于i-1及之前位置的可偷金额的最大值。
- 通过比较两种情况,确定i位置可偷金额的最大值dp[i]。
- 我们将每个位置的最大值可偷金额存储在dp[i]中,则会动态的求解出后续位置的最大值可偷金额。
3、复杂度分析:
① 时间复杂度:O(n),其中 n 是数组长度。只需要对数组遍历一次。
② 空间复杂度:O(n),使用dp数组进行记录。
思路二(动态规划(滚动数组)):
1、由思路一可知 dp[i]=max{dp[i-1],dp[i-2]+nums[i]},我们只需维护 dp[i-1] 和 dp[i-2] 两个值就可以。
2、复杂度分析
① 时间复杂度:O(n),其中 n 是数组长度。只需要对数组遍历一次。
② 空间复杂度:O(1),使用滚动数组。
代码实现
代码实现(思路一(动态规划(一维dp数组))):
cpp
class Solution1 {
public:
int rob(vector<int>& nums) {
//dp[i]与dp[i-1]和dp[i-2]相关所以先确定前两项
if (nums.size()==0) return 0;
if (nums.size()==1) return nums[0];
//dp[i]代表当前下标最多能偷的金额
vector<int> dp(nums.size());
//dp[i]与dp[i-1]和dp[i-2]相关所以先确定前两项
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
//动态的求解其他最多能偷的金额:dp[i]=max{dp[i-1],dp[i-2]+nums[i]}
for (int i = 2; i < nums.size(); i++){
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.size()-1];
}
};
代码实现(思路二(动态规划(滚动数组))):
cpp
class Solution2{
public:
int rob(vector<int>& nums) {
//dp[i]与dp[i-1]和dp[i-2]相关所以先确定前两项
if (nums.size()==0) return 0;
if (nums.size()==1) return nums[0];
//dpi_2代表dp[i-2],dpi_1代表dp[i-1],dpi代表dp[i]
int dpi_2=nums[0];
int dpi_1=max(nums[0],nums[1]);
int dpi=dpi_1;
//动态的求解其他最多能偷的金额:dp[i]=max{dp[i-1],dp[i-2]+nums[i]}
for (int i = 2; i < nums.size(); i++){
dpi=max(dpi_2+nums[i],dpi_1);
dpi_2=dpi_1;
dpi_1=dpi;
}
return dpi;
}
};
以思路一为例进行调试
cpp
#include<iostream>
#include<vector>
using namespace std;
class Solution1 {
public:
int rob(vector<int>& nums) {
//dp[i]与dp[i-1]和dp[i-2]相关所以先确定前两项
if (nums.size()==0) return 0;
if (nums.size()==1) return nums[0];
//dp[i]代表当前下标最多能偷的金额
vector<int> dp(nums.size());
//dp[i]与dp[i-1]和dp[i-2]相关所以先确定前两项
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
//动态的求解其他最多能偷的金额:dp[i]=max{dp[i-1],dp[i-2]+nums[i]}
for (int i = 2; i < nums.size(); i++){
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.size()-1];
}
};
int main()
{
vector<int> nums={1,2,3,1};
Solution1 s;
cout<<s.rob(nums);
return 0;
}
LeetCode 热题 100_打家劫舍(83_198)原题链接
欢迎大家和我沟通交流(✿◠‿◠)