1、打家劫舍【LC 198题】
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
动态规划五部曲:
dp数组以及下标的含义
dp[i]表示到第i家,能获得的最大金额
递推公式
分为两种情况:偷第i家和不偷第i家
dp[i]=max(dp[i-1],dp[i-2]+nums[i])
dp数组初始化
dp[0]=nums[0],dp[1]=max(dp[0],dp[1])
遍历顺序
从前往后遍历
打印dp数组【答案错误的时候用于排查】
package DynamicProgramming.Rob;
public class Rob {
public int rob(int[] nums) {
if(nums.length==1) return nums[0];
nums[1]=Math.max(nums[0],nums[1]);
for (int i = 2; i < nums.length; i++) {
nums[i]=Math.max(nums[i-1],nums[i-2]+nums[i]);
}
return nums[nums.length-1];
}
}
空间优化:因为只需要到前面第一家的最大金额,和到前面第二家的最大金额,所以只用a,b,c三个变量分别表示以上两个变量和到当前家最大金额。
注意:赋值计算的时候,需要表示利用上一次的c来表示这一次的b,因此第一步需要b=c;需要不变的上两次的a,因此a=b需要放在最后一步
public int rob2(int[] nums) {
if(nums.length==1) return nums[0];
int a=0,b=0,c=0;
for (int i = 0; i < nums.length; i++) {
b=c;
c=Math.max(nums[i]+a,b);
a=b;
}
return c;
}
2、打家劫舍II【LC 213题】
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
思路:
和上道题类似。只不过房间成为环形了,只要把环形房间分成几种情况就好了。
1)首尾都不偷,只偷中间的房间
2)只偷前n-1个房间,最后一个房间不偷
3)第一个房间不偷,偷后n-1个房间。
这样就把环形房间的问题转换成线性房间的问题了,再取三种情况的最大值即可。
经过分析:第二种情况包含了第一种情况,因为我们只是考虑前n-1个房间,不一定会偷第一个房间。
package DynamicProgramming.RobSeries;
public class Rob2 {
public int rob(int[] nums) {
if (nums.length==1) return nums[0];
int len=nums.length;
return Math.max(robLinear(nums,0,len-2),robLinear(nums,1,len-1));
}
private int robLinear(int[] nums, int start, int end) {
int a=0,b=0,c=0;
for (int i = start+1; i <= end; i++) {
c=Math.max(nums[i]+a,b);
a=b;
b=c;
}
return c;
}
}
3、打家劫舍III【LC 337题】
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root
。
除了 root
之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root
。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
思路:
树形dp。
对应每一个节点都有两种状态,偷当前节点和不偷当前节点。
1)偷当前节点的情况下,res=root.val+不偷左节点的最大值+不偷右节点的最大值
2)不偷当前节点的情况下,res=max(偷左节点的最大值,不偷左节点的最大值)+max(偷右节点的最大值,不偷右节点的最大值)
=》因此,用一个固定大小为2的数组存储每个节点的两种状态。
这道题的遍历顺序为从叶子节点到根节点【递归体现】,最后返回根节点两种状态的较大值。
package DynamicProgramming.RobSeries;
import DataStruct.TreeNode;
public class Rob3 {
public int rob(TreeNode root) {
int []res=robAction(root);
return Math.max(res[0],res[1]);
}
private int[] robAction(TreeNode root) {
if (root==null) return new int[2];
int []res=new int[2];
int []left=robAction(root.left);
int []right=robAction(root.right);
res[1]=root.val+left[0]+right[0]; //res[1]表示偷当前节点
res[0]=Math.max(left[0],left[1])+Math.max(right[0],right[1]); //res[0]表示不偷当前节点
return res;
}
}