打家劫舍
题目:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
思路:因为不能连续偷两个房间,所有下标为i到房间偷不偷可以有两个方向来;第一,偷下标为i的房间那么下标i-1的房间就不能偷,那么最大金额就是由下标为i-2的房间及以前的房间决定的
,第二,不偷下标为i的房间,那么最大金额就是由下标为i-1的房间及以前决定的
(注意i-1的房间不一定偷,波偷不偷又是由i-1前的i-1-1和i-1-2的两个方向来决定的)
- dp[i]的含义:下标前i的房间(包含i),能够偷到的最大金额为dp[i]
- 递推公式:dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1])
- dp[i-2]+nums[i]:偷下标为i的房间,所有加上了下标为i的房间的金额
- dp[i-1]:不偷下标为i的房间,那么前i-1个房间最大金额,就是当前的最大金额
- dp数组初始化:dp[0] = nums[0],dp[1] = Math.max(nums[0],nums[1])
- 遍历顺序:从小到大
- 打印dp数组
class Solution {
public int rob(int[] nums) {
if(nums.length == 1){
return nums[0];
}
// dp[i] 表示下标前i个房间(包括i),能够偷到的最大金额是dp[i]
int[] dp = new int[nums.length];
// 初始化
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2;i<nums.length;i++){// 物品
dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.length-1];
}
}
打家劫舍Ⅱ
题目:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
思路:我们可以把环拆开,我们可以分成三种情况,情况一:我们都不考虑首尾元素,情况二:我们只考虑首元素,情况三:我们只考虑尾元素
class Solution {
public int rob(int[] nums) {
if(nums.length == 1) return nums[0];
// 可以分成两个情况
// 只考虑首元素
int[] head = Arrays.copyOfRange(nums,0,nums.length-1);
int headMoney = getMoney(head);
// 只考虑尾元素
int[] tail = Arrays.copyOfRange(nums,1,nums.length);
int tailMoney = getMoney(tail);
return Math.max(headMoney,tailMoney);
}
private int getMoney(int[] nums){
if(nums.length == 1) return nums[0];
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2;i<nums.length;i++){
dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.length - 1];
}
}
打家劫舍Ⅲ
题目:小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
思路:我们可以使用一个大小为2的一维数组来表示某个节点是否偷,如果不偷下标0表示不偷的最大金额,如果偷下标1表示偷的最大金额
- dp[0]、dp[1]的含义:dp[0]表示当前节点不偷的最大金额,dp[1]表示当前节点偷的最大金额
- 递推公式:当前的节点的最大金额就是Math.max(dp[0],dp[1])
- dp数组初始化:dp[0] = 0,dp[1] = 0
- 遍历顺序:后序遍历
- 打印dp数组
class Solution {
public int rob(TreeNode root) {
// 我们使用一个大小为2的一维数组来表示当前节点偷和不偷的最大金额
// {i,j}表示:当前节点不偷的最大金额为i,偷的最大金额为j
int[] res = robTree(root);
return Math.max(res[0],res[1]);
}
private int[] robTree(TreeNode node){
if(node == null) return new int[]{0,0};
// 左
int[] leftDp = robTree(node.left);
// 右
int[] rightDp = robTree(node.right);
// 中
// 偷当前节点
// 说明左右孩子不能偷,所有当前节点的值加上左右孩子的dp数组下标为0(不偷)的值
int leftVal = node.val + leftDp[0] + rightDp[0];
// 不偷当前节点
// 说明当前节点不偷的最大金额,去绝对左右孩子偷或不偷的最大金额加起来
int rightVal = Math.max(leftDp[0],leftDp[1]) + Math.max(rightDp[0],rightDp[1]);
return new int[]{rightVal,leftVal};
}
}