198.打家劫舍
力扣题目链接
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
- 示例 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.确定dp数组(dp table)以及下标的含义
dp[i] 下标为i以内的房屋,最多可以偷的金额为dp[i]
2.确定递推公式
决定dp[i]的因素就是第i房间偷还是不偷。
如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,即:第i-1房一定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为dp[i-2] 加上第i房间偷到的钱。
如果不偷第i房间,那么dp[i] = dp[i - 1],即考 虑i-1房
dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1])
3.初始化
dp[0]=nums[0];
dp[1]=Math.max(dp[0],dp[1]);
4.确定遍历顺序
dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历!
5.打印数组
以示例二,输入[2,7,9,3,1]为例。
红框dp[nums.size() - 1]为结果。
代码如下:
class Solution {
public int rob(int[] nums) {
int[] dp=new int[nums.length+1];
dp[0]=nums[0];
if(nums.length>1){
dp[1]=Math.max(nums[0],nums[1]);
}
for(int i=2;i<nums.length;i++){
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[nums.length-1];
}
}
- 时间复杂度: O(n)
- 空间复杂度: O(n)
213.打家劫舍II
力扣题目链接
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。
示例 1:
- 输入:nums = [2,3,2]
- 输出:3
- 解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
- 示例 2:
- 输入:nums = [1,2,3,1]
- 输出:4
- 解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
- 示例 3:
- 输入:nums = [0]
- 输出:0
提示:
首尾连成环了
起点是哪,终点是哪,选不选呢
分情况
- 考虑不包含首尾元素
- 考虑首元素
- 考虑尾元素
后两个情况包含第一个情况
class Solution {
public int rob(int[] nums) {
if(nums.length==0) return 0;
if(nums.length==1) return nums[0];
int result1=robAction(nums,0,nums.length-2); //考虑首元素
int result2=robAction(nums,1,nums.length-1); //考虑尾元素
return Math.max(result1,result2);
}
public int robAction(int[] nums,int start,int end){
if(start==end) return nums[start];
int[] dp=new int[nums.length+1];
dp[start]=nums[start];
dp[start+1]=Math.max(nums[start+1],nums[start]);
for(int i=start+2;i<nums.length;i++){
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[end];
}
}
337.打家劫舍 III
力扣题目链接
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
后序遍历树,然后按上两题的逻辑
那么下面我以递归三部曲为框架,其中融合动规五部曲的内容来进行讲解。
1.确定递归函数的参数和返回值
int[] result=robTree(root)
dp[0]表示不偷当前节点得到的最大金钱, dp[1]表示偷当前节点得到的最大金钱
本题dp数组就是一个长度为2的数组!
2.确定终止条件
遇到空节点 偷不偷都是0
int res[] = new int[2];
if(root==null) return res;
这也相当于dp数组的初始化
3.确定遍历顺序
后序遍历
通过递归左节点,得到左节点偷与不偷的金钱。
通过递归右节点,得到右节点偷与不偷的金钱。
int[] left = robAction1(root.left);
int[] right = robAction1(root.right);
4.确定单层递归的逻辑
// 不偷:Max(左孩子不偷,左孩子偷) + Max(又孩子不偷,右孩子偷)
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
// 偷:左孩子不偷+ 右孩子不偷 + 当前节点偷
res[1] = root.val + left[0] + right[0];
return res;
5.举例推导dp数组
以示例1为例,dp数组状态如下:(注意用后序遍历的方式推导)
最后头结点就是 取下标0 和 下标1的最大值就是偷得的最大金钱。
class Solution {
public int rob(TreeNode root) {
int[] res=robAction(root);
return Math.max(res[0],res[1]);
}
public int[] robAction(TreeNode root){
int[] dp=new int[2];
if(root==null) return dp;
int[] left=robAction(root.left);
int[] right=robAction(root.right);
dp[0]=Math.max(left[0],left[1])+Math.max(right[0],right[1]);
dp[1]=root.val+left[0]+right[0];
return dp;
}
}