打家劫舍(动态规划)
1、你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
2、给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
方法一:记忆化搜索:递归:自顶向下:
//使用记忆化搜索(剪枝):memo[i]表示考虑抢劫num[i...n-1]的房子所能获得的最大收益
int[] memo;
/**
* 考虑抢劫nums[index...nums.length]这个范围内的所有房子
* @param nums
* @param index
* @return
*/
private int tryRob(int[] nums,int index){
//递归终止条件
if(index >= nums.length){
return 0;
}
//判断memo[index]中是否有值,有值的话不用继续计算了,直接返回
if(memo[index] != -1){
return memo[index];
}
int res = 0;
//考虑抢劫从index到nums.length位置的宝物
for(int i=index;i<nums.length;i++){
//递归调用,更新res
res = Math.max(res,nums[i]+tryRob(nums,i+2));
}
//将计算的值记录下来
memo[index] = res;
return res;
}
/**
* 打家劫舍:递归解决(自顶向上)(使用记忆化搜索进行剪枝)
* @param nums
* @return
*/
public int rob(int[] nums) {
memo = new int[nums.length];
for (int i=0;i<memo.length;i++){
memo[i]=-1;
}
return tryRob(nums,0);
}
方法二:动态规划:自底向上:
/**
* 打家劫舍:动态规划(自底向上):找到最基础的子问题,对状态的定义,对状态的转移
* @param nums
* @return
*/
public int rob(int[] nums) {
int n = nums.length;
//输入空数组:没有房子可以偷(小心数组越界n-1)
if(n == 0){
return 0;
}
//memo[i]表示考虑抢劫num[i...n-1]房子所能获得的最大收益
//我们最后返回memo[0],就是我们要求的最大收益(考虑抢劫num[0...n-1]房子所能获得的最大收益)
int[] memo = new int[n];
for(int i=0;i<n;i++){
memo[i] = -1;
}
//最基础的子问题:只考虑偷取nums[n-1...n-1]的房子,只有一个房子,偷这个房子
memo[n-1] = nums[n-1];
//状态转移
for(int i = n-2;i>=0;i--){
//求解memo[i]:考虑抢劫num[i...n-1]房子所能获得的最大收益
for(int j = i;j<n;j++){
//尝试偷取nums[j]的房子,然后继续考虑偷取nums[j+2...n-1]的房子所能获得的最大收益
memo[i] = Math.max(memo[i],nums[j]+(j+2 < n ? memo[j+2]:0));
}
}
return memo[0];
}
class Solution {
public int rob(int[] nums) {
if(nums.length ==0){
return 0;
}
//定义状态
int[] dp = new int[nums.length+1];
//状态初始化
dp[0] = 0;//没有房子
dp[1] = nums[0];//只有一个房子,偷这个房子即可
for(int i = 2;i<=nums.length;i++){
//状态转移方程
dp[i] = Math.max(dp[i-1],nums[i-1]+dp[i-2]);
}
return dp[nums.length];
}
}