目录
198.打家劫舍
看到题目的第一想法
看到代码随想录之后的想法
自己实现过程中遇到的困难
213.打家劫舍II
看到题目的第一想法
看到代码随想录之后的想法
自己实现过程中遇到的困难
337.打家劫舍 III(树形dp)
看到题目的第一想法
看到代码随想录之后的想法
自己实现过程中遇到的困难
198.打家劫舍
力扣题目链接(opens new window)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
- 示例 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 。
提示:
- 0 <= nums.length <= 100
- 0 <= nums[i] <= 400
看到题目的第一想法
dp[j]设置为能偷到的最高金额
由于相邻的不能偷于是最高金额应该从j-2,j-3种去取
dp[j] = nums[i]+math.Max(dp[j-2],dp[j-3]);
最终返回dp[j]与dp[j-1]中的最大值
看到代码随想录之后的想法
我的想法并没有完全按照dp数组的定义来写,不具备普适性
代码随想录中,dp的定义为能偷到的最高金额
则目标元素应该有两种选择
1 偷目标元素 :若偷目标元素为dp[j-2]+nums[i]
2 不偷目标元素:若不偷目标元素为dp[j-1]
dp[j] = Math.max(dp[j-2]+nums[i],dp[j-1]);
自己实现过程中遇到的困难
初始化时 dp[0] = nums[0]
dp[1] = Math.max(nums[0],nums[1]); 应该是取两者的最大值,才能保证偷到最多
我写成了dp[1] = nums[1]
class Solution {/*public int rob(int[] nums) {// 定义dp数组以及每个下标的含义// 到达dp[i] 能偷窃到的最高金额// 确定递归函数// dp[i] = nums[i]+Math.max(dp[i-2],dp[i-3]);// 确定遍历顺序// 从前往后// dp数组初始化// dp[0] = nums[0], dp[1] = nums[1] , dp[2] = nums[2]+nums[0] // dp3 = nums[i]+Math.max(dp[i-2],dp[i-3])// 举例推导dp数组int[] dp = new int[nums.length];if(nums.length==1){return nums[0];}if(nums.length==2){return Math.max(nums[0],nums[1]);}if(nums.length==3){return Math.max(nums[0]+nums[2],nums[1]);}dp[0] = nums[0];dp[1] = nums[1];dp[2] = nums[2] + nums[0];for(int i=3;i<nums.length;i++){dp[i] = nums[i]+Math.max(dp[i-2],dp[i-3]);}return Math.max(dp[nums.length-2],dp[nums.length-1]);}*///卡哥思路public int rob(int[] nums) {//我的方法没有遵循dp数组的定义,dp[i] 代表当前最高金额// 当前最高金额,如果偷当前nums[i] 则dp[i] = nums[i]+dp[i-2]// 如果不偷当前nums[i] 则为dp[i-1]// 定义dp数组以及每个下标的含义// 考虑下标i 能偷到的最高金额// 确定递归函数// 对于nums[i] 我们能考虑是偷还是不偷// 若偷i 则 dp[i] = nums[i]+dp[i-2]// 若不偷i 则 dp[i] = dp[i-1]// dp[i] = nums[i]+Math.max(dp[i-2],dp[i-3]);// 确定遍历顺序// 从前往后// dp数组初始化// dp[0] = nums[0], dp[1] = nums[1] , dp[2] = nums[2]+nums[0] // dp3 = nums[i]+Math.max(dp[i-2],dp[i-3])// 举例推导dp数组int[] dp = new int[nums.length];if(nums.length==1){return nums[0];}if(nums.length==2){return Math.max(nums[0],nums[1]);}dp[0] = nums[0];//这里应该是Math.max(nums[0],nums[1]);我之前直接写成nums[1] 是错误的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];}
}
213.打家劫舍II
力扣题目链接(opens new window)
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。
示例 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
看到题目的第一想法
和打家劫舍1差不多,只是要考虑首尾
先处理[0,nums[length-2]]
再处理[1,nums[length-1]]
返回两者的最大值
看到代码随想录之后的想法
和我思路差不多
自己实现过程中遇到的困难
初始化时 dp[0] = nums[0]
dp[1] = Math.max(nums[0],nums[1]); 应该是取两者的最大值,才能保证偷到最多
我写成了dp[1] = nums[1]
class Solution {public int rob(int[] nums) {// 确定dp以及每个下标的含义// 第一个房屋和最后一个是紧挨着的// 主要是没办法确定是否加上了第一个// 今晚能偷窃到的最高金额// 我的思路 ,两次dp 一次从第一个到倒数第二个// 一次从第二个到最后一个。// 确定递推函数// dp[i] = Math.max(nums[i]+dp[i-2],dp[i-1]);// 确定遍历顺序// 从前往后// dp数组初始化// dp[0] = nums[0],dp[1]=Math.max(nums[0],nums[1]);// 举例推导dp数组int[] dp1 = new int[nums.length-1];int[] dp2 = new int[nums.length-1];if(nums.length==1){return nums[0];}if(nums.length==2){return Math.max(nums[0],nums[1]);}if(nums.length==3){return Math.max(Math.max(nums[0],nums[1]),nums[2]);}dp1[0] = nums[0];dp1[1] = Math.max(nums[0],nums[1]); dp2[0] = nums[1];dp2[1] = Math.max(nums[1],nums[2]);for(int i=2;i<nums.length-1;i++){dp1[i] = Math.max(dp1[i-1],nums[i]+dp1[i-2]);dp2[i] = Math.max(dp2[i-1],nums[i+1]+dp2[i-2]);}return Math.max(dp1[nums.length-2],dp2[nums.length-2]);}
}
337.打家劫舍 III(树形dp)
力扣题目链接(opens new window)
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
看到题目的第一想法
应该使用什么递归思路?前序?后续?
dp数组要怎么定义?无思路
看到代码随想录之后的想法
卡哥更多的是考虑二叉树的遍历
每个节点两种状态,偷该节点,不偷该节点
要获取偷该节点的最大值,和不偷该节点的最大值
使用后序遍历,需要通过子节点偷还是不偷,来确定父节点是否要偷
每层递归通过一个数组dp[2] 来告诉上一层,偷与不偷的值
new int{偷该层的最大值,不偷该层的最大值}
父节点通过左右孩子的返回值来确定偷or不偷的最大值,再往上返回
若该节点要偷:左右孩子不偷的值相加,同时与自己的值相加
dp[0] = left[0]+right[0]+val
若该节点不偷:左右孩子偷与不偷的最大值
dp[1] = Math.max(left[0],left[1])+Math.max(right[0],right[1])
递归三要素
1 确定递归函数的参数和返回值
参数为root,返回值为int[]
2 确定递归函数的终止条件
若root==null 则return new int[]{0,0}
3 确定递归函数的执行逻辑
后序遍历
先递归执行root.left
再递归执行root.right
通过left和right返回的int数组,来确定自身的int[]并返回
确定dp数组以及下标的含义
确定递推函数
dp数组初始化
确定遍历顺序
举例推导dp数组
自己实现过程中遇到的困难
树形dp没见过,考虑在每个递归中新增一个dp[] 来确保内容的传递
确定每个元素的动作比较关键,比如这个题目就是要你看该层是偷or不偷?
然后分别讨论
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public int rob(TreeNode root) {//卡哥思想// 确定递归的参数和返回值// 参数 root ,返回值 dp[2] // 确定递归的终止条件// 如果为null return {0,0}// 确定递归函数的执行逻辑// 看是否偷当前节点,分为偷or不偷// 这道题是树形dp的基础题目// 使用一个数组,记录两个状态{偷当前的最高金额,不偷当前的最高金额}// 通过父节点看子节点中的金额// 每个节点有两个状态:// 偷:子节点不偷,将两个子节点不偷的值(left[1],right[1])+自己节点的值// 不偷:子节点偷or不偷的最大值相加,max(left[0],left[1])+max(right[0],right[1])//确定dp数组和下标的含义// dp[0] 偷的最高金额// dp[1] 不偷的最高金额//用数组来记录小偷能偷取的最大值//确定递推公式// dp[0] = left[1]+right[1]+val;// dp[1] = Math.max(left[0],left[1])+max(right[0],right[1])//DP数组初始化//都为0//确定遍历顺序//后续遍历//举例推导dp数组int[] dp = getMax(root);return Math.max(dp[0],dp[1]);}int[] getMax(TreeNode root){if(root==null){return new int[2];}//后续遍历,左右中int[] left = getMax(root.left);int[] right = getMax(root.right);int[] dp = new int[2];//看是否偷当前的元素//偷当前的元素,则左右不偷dp[0] = left[1]+right[1] + root.val;//不偷当前的元素dp[1] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);return dp;}
}