今日收获:打家劫舍,打家劫舍Ⅱ,打家劫舍Ⅲ
1. 打家劫舍
思路:
(1)dp数组:表示下标 i 及之前能偷盗的最大金额。
(2)初始化:数组的前两个下标能偷盗的最大值。
(3)递推公式:当前位置是否偷要根据前两个位置取最大值推出来。dp[i-2]+nums[i] 表示偷盗当前位置;dp[i-1] 表示不偷盗当前位置。
注意要判断数组下标是否越界。
方法:
class Solution {
public int rob(int[] nums) {
int len=nums.length;
if (len==1){
return nums[0];
}
// 下标i及之前偷盗的最大值
int[] dp=new int[len];
// 初始化
dp[0]=nums[0];
dp[1]=nums[0]>nums[1]?nums[0]:nums[1];
// 当前结果取决于前两个结果,从左到右遍历
for (int i=2;i<len;i++){
dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]); // 偷与不偷是个问题
}
return dp[len-1];
}
}
2. 打家劫舍Ⅱ
题目链接:213. 打家劫舍 II - 力扣(LeetCode)
思路:将环形问题转换为线性数组问题,就可以转化为线性的打家劫舍问题。因为头尾不能同时选择,所以可以分成两种情况:不考虑尾实体 & 不考虑头实体,再求两者中用线性打家劫舍求得结果的最大值。
方法:
class Solution {
public int rob(int[] nums) {
int len=nums.length;
if (len==1){
return nums[0];
}
if (len==2){
return Math.max(nums[0],nums[1]);
}
int dpHead=array(nums,0,len-1); // 不考虑尾元素
int dpTail=array(nums,1,len); // 不考虑头元素
return dpHead>dpTail?dpHead:dpTail;
}
public int array(int[] nums,int start,int end){ // 左闭右开区间
int len=end-start;
// 下标i及之前偷盗的最大值
int[] dp=new int[len];
// 初始化
dp[0]=nums[start];
dp[1]=nums[start]>nums[start+1]?nums[start]:nums[start+1];
// 当前结果取决于前两个结果,从左到右遍历
for (int i=2;i<len;i++){
dp[i]=Math.max(dp[i-2]+nums[start+i],dp[i-1]); // 偷与不偷是个问题
}
return dp[len-1];
}
}
3. 打家劫舍Ⅲ
题目链接:337. 打家劫舍 III - 力扣(LeetCode)
思路:后序递归遍历。
(1)dp数组定义:长度为2,两个元素分别表示偷当前节点和不偷当前节点的最大值。
(2)递推公式:如果偷当前节点,则数组的值为当前节点的值加上不偷左右孩子的值;如果不偷当前节点,则分别比较左右孩子的dp数组元素最大值再相加。
方法:
class Solution {
public int rob(TreeNode root) {
// 二叉树递归遍历
int[] dp=robTree(root); // dp数组的长度为2,分别表示偷和不偷当前节点
return Math.max(dp[0],dp[1]);
}
public int[] robTree(TreeNode node){
if (node==null){
return new int[]{0,0};
}
int[] left=robTree(node.left);
int[] right=robTree(node.right);
int[] dp=new int[2];
// 偷当前节点,不偷左右孩子
dp[1]=node.val+left[0]+right[0];
// 不偷当前节点
dp[0]=Math.max(left[0],left[1])+Math.max(right[0],right[1]);
return dp;
}
}