问题描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
问题分析
该问题是经典的动态规划入门题,首先动态规划有四个基本步骤:
- 确定子问题
- 写出递推关系(状态转移方程)
- 确定 d p dp dp数组计算顺序(自顶向下/自底向上)
- 空间优化(如滚动数组)
该题目说相邻房屋不能同时偷窃,其实入门时第一反应是想到了要么偷所有奇数,要么偷所有偶数,但是这显然是忽略了…
![[Pasted image 20230209184559.png]]
因此还是要按动态规划的思维来想问题,相邻的房屋不能抢,设子问题是k间房屋抢到最多的金额,即dp[k]
。
状态转移方程
要得到递归关系式,对于该问题关键就是相邻两个房间不能同时偷窃,对于子问题来说就是第k
间房选中与否,而是否选中则取决于dp[k-2]+nums[k]
和dp[k-1]
的大小,故可得到状态转移方程:
中,选择金额较大的一种结果。
d
p
(
k
)
=
max
{
d
p
(
k
−
1
)
,
H
k
−
1
+
d
p
(
k
−
2
)
}
dp(k)=\max \{ dp(k-1), H_{k-1} + dp(k-2) \}
dp(k)=max{dp(k−1),Hk−1+dp(k−2)}
代码
动态规划的代码中,dp数组的大小通常创建为n+1
,即保留一个空的dp[0][0]
从而避免单独考虑边界情况,但要注意下标和所给数据下标的非一致性。若统一下标可如下:
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n==1) return nums[0];
int[] dp = new int[n]; //
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(int i=2;i<n;i++)
{
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]); //统一dp中i和nums中i的含义
}
return dp[n-1];
}
}
滚动数组优化
由于dp[k]
只与dp[k-1]和dp[k-2]
有关,我们只需两个量来存储后者,之后通过滚动数组的方式实现,可以降低空间复杂度。
class Solution {
public int rob(int[] nums) {
int prev=0;
int curr=0;
for(int i:nums){
int temp = Math.max(prev+i,curr);
prev = curr;
curr = temp;
}
return curr;
}
}