你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。
动态规划问题的特点:
- 问题可以被划分为若干
重叠
子问题- 子问题可以通过已知的子问题求解,且子问题可以重复利用
- 需要一个数据结构来存储子问题的解,以便在使用时取出
为什么这题能够使用动态规划?
解题步骤:
- 将原问题转化为一个或多个子问题
- 定义问题的状态转移方程
- 设置边界条件
- 计算子问题的解并记录
- 由子问题的解得到原问题的解
此时,我们定义一个二维数组 d p [ n ] [ 2 ] dp[n][2] dp[n][2]来存储子问题的解:
n n n表示一共有 n n n个重叠子问题
d p [ k ] [ 0 ] dp[k][0] dp[k][0]表示:房屋数量为 k k k时,且第 k + 1 k+1 k+1家不偷窃时最大的偷窃金额
d p [ k ] [ 1 ] dp[k][1] dp[k][1]表示:房屋数量为 k k k时,且第 k + 1 k+1 k+1家偷窃时最大的偷窃金额
设问题【当有 k k k家房屋时,最大得偷窃金额】表示为 f ( k ) f(k) f(k),根据上面的等式,此时 f ( k ) f(k) f(k)可表示成两个子问题:
f ( k ) = { f ( k − 1 ) k 不偷 n u m s ( k ) + d p [ k − 1 ] [ 0 ] k 偷 f(k)=\begin{cases} f(k-1) & k不偷 \\ nums(k) +dp[k-1][0] & k偷 \end{cases} f(k)={f(k−1)nums(k)+dp[k−1][0]k不偷k偷
f ( k − 1 ) f(k-1) f(k−1)分别有 第 k − 1 k-1 k−1家偷 和 第 k − 1 k-1 k−1家不偷下对应的解,我们取最大即可:
f ( k − 1 ) = M A X { d p [ k − 1 ] [ 0 ] , d p [ k − 1 ] [ 1 ] } f(k-1)=MAX\{dp[k-1][0],dp[k-1][1]\} f(k−1)=MAX{dp[k−1][0],dp[k−1][1]}
最终状态转移方程,可得:
f ( k ) = { M A X { d p [ k − 1 ] [ 0 ] , d p [ k − 1 ] [ 1 ] } k 不偷 n u m s ( k ) + d p [ k − 1 ] [ 0 ] k 偷 f(k)=\begin{cases} MAX\{dp[k-1][0],dp[k-1][1]\} & k不偷 \\ nums(k) +dp[k-1][0] & k偷 \end{cases} f(k)={MAX{dp[k−1][0],dp[k−1][1]}nums(k)+dp[k−1][0]k不偷k偷
从状态转移方程看,我们需要从左到右的更新dp数组。
最后,附上Java实现方式:
class Solution {
public int rob(int[] nums) {
//len表示有几家房子
int len = nums.length;
//定义dp数组存储子问题的解,原问题的解存储在dp[len-1]中
int dp[][] = new int[len][2];
//边界条件
dp[0][0] = 0;//第一家不偷
dp[0][1] = nums[0];//第一家偷
//从左到右依次更新
for(int i=1;i<len;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]);
dp[i][1] = dp[i-1][0]+nums[i];
}
return Math.max(dp[len-1][0],dp[len-1][1]);
}
}
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,今晚能够偷窃到的最高金额。
相比上面打家劫舍1.0版本,多了一个限制条件:首位相连
如何在上面的基础上考虑第一家和最后一家呢?
现在仅关注第一家:
可以看到,当对第一家不偷时,那第一家可当作可有可无的,直接将第一家忽略,即只有第2~n家,转变成1.0版本形式
如果偷第一家,那么最后一家就不能偷,同理第n家可以被忽略
所以,可以分成两种情况:
最后,附上Java代码
class Solution {
public int rob(int[] nums) {
int len = nums.length;
//当家的数量等于1或者2的情况
if(len==1)return nums[0];
if(len<3)return Math.max(nums[0],nums[1]);
//用于记录最大数额的变量
int ans = 0;
//记录子问题答案的数组
int dp[][] = new int[len][2];
//当第1家不偷,对1~len-1执行1.0的算法
//忽略dp[0] 边界条件从1开始
dp[1][0] = 0;dp[1][1] = nums[1];
for(int i=2;i<len;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]);
dp[i][1] = dp[i-1][0] + nums[i];
}
//记录第1家不偷能获取的最大金额
ans = Math.max(dp[len-1][0],dp[len-1][1]);
//第1家偷,忽略最后一家,对0~len-2执行1.0的算法
dp[0][0]=0;
dp[0][1]=nums[0];
for(int i=1;i<len-1;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]);
dp[i][1] = dp[i-1][0] + nums[i];
}
//更新最大金额
ans = Math.max(Math.max(dp[len-2][0],dp[len-2][1]),ans);
return ans;
}
}