这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 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
解题
解题思路
打家劫舍是一道很经典的面试题。很多算法书上都有类似的例子题,主要用来介绍动态规划。根据这道题的条件特点:如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警(即相邻的数字不能同时作为最终求和的有效数字)。
所以我们设计动态规划的三个步骤,将问题分解成最优子问题。用递归的方式将问题表述成最优子问题的解;自底向上的将递归转化成迭代。
对于连续的 nn 栋房子:H1,H2,H3......HnH 1 ,H 2 ,H 3 ......H n ,小偷挑选要偷的房子,且不能偷相邻的两栋房子,方案无非两种:
方案一:挑选的房子中包含最后一栋;
方案二:挑选的房子中不包含最后一栋;
即给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 当前:[2,7,9,3,1,10] 目标:21(2+9+10 = 21)
解题过程
- 设置动态规划方程:dp[n] = MAX( dp[n-1], dp[n-2] + num )
- 由于不可以在相邻的房屋闯入,所以在当前位置 n 房屋可盗窃的最大值,要么就是 n-1 房屋可盗窃的最大值,要么就是 n-2 房屋可盗窃的最大值加上当前房屋的值,二者之间取最大值
- 举例来说:1 号房间可盗窃最大值为 33 即为 dp[1]=3,2 号房间可盗窃最大值为 44 即为 dp[2]=4,3 号房间自身的值为 22 即为 num=2,那么 dp[3] = MAX( dp[2], dp[1] + num ) = MAX(4, 3+2) = 5,3 号房间可盗窃最大值为 55
- 时间复杂度:O(n)O(n),nn 为数组长度
代码实现
class Solution {
public int rob(int[] nums) {
//子问题:问题规模就是房屋数量,到当前收益等于前一家不偷收益加上自家收益,和前偷次不偷的最大者。
//存储结构:二维数组,i为当前偷或不偷,j为当前的房屋
int[][]dp = new int[2][nums.length];
//初始化第一间收益
dp[0][0] = 0;
dp[1][0] = nums[0];
for(int i=1;i<nums.length;i++){
//这间不偷,前面一定偷
//上面错了,应该是可能偷也可能不偷
dp[0][i] = Math.max(dp[1][i-1],dp[0][i-1]);
//这间偷了,前面不能偷
dp[1][i] = dp[0][i-1]+nums[i];
}
return Math.max(dp[0][nums.length-1],dp[1][nums.length-1]);
}
}
空间优化
本题状态转移是顺着数组滚动的,并且一个数组位置只有两个状态,因此可以用常量记录进行空间优化。
class Solution {
public int rob(int[] nums) {
//初始化两个变量,一个表示偷,一个不偷
int r = nums[0],n = 0;
for(int i=1;i<nums.length;i++){
//这间不偷,上一家可能偷也可能不偷
int tmp = n;//暂存上一家不偷的收益
n = Math.max(n,r);
//这间偷了,前面不能偷,等于保存的上家收益加这间房屋的收益
r = tmp + nums[i];
}
return Math.max(n,r);
}
}
关于打家劫舍的思考和方法今天就分享到这里,同学想看到关于算法面试中的哪些疑难解析,可以给我留言,我们一起再来探讨,我们下篇文章再见。