198.打家劫舍
文章讲解:https://programmercarl.com/0198.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.html
题目链接:https://leetcode.cn/problems/house-robber/
视频讲解:https://www.bilibili.com/video/BV1Te411N7SX/
套动态规划五步骤去做,但是没想到切入点,卡在递推公式的推导以及dp的定义上。不知道该如何确定i的概念。
动态规划去处理该题,i是输入元素的下标,然后递推公式按照i这个索引的元素放不放去推导出来,具体实现:
遍历的时候,i的取值没确定下来,是i<=nums.length还是i<nums.length。
这里思考了一下,因为最终求得后的dp数组要返回的值应该是nums的最后一个元素的位置,因此应该返回dp[nums.length - 1]。并且在遍历过程中,p[i] = Math.max(dp[i-2] + nums[i],dp[i-1]),nums[i]也只能到达i=nums.length - 1,因此这里的循环处理逻辑为i<nums.length。最终代码:
public int rob(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-1],dp[i-2] + nums[i]);
}
return dp[nums.length - 1];
}
这道题的核心就是dp[i]的概念和递推公式的确定。
213.打家劫舍II
文章讲解:https://programmercarl.com/0213.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8DII.html
题目链接:https://leetcode.cn/problems/house-robber-ii/
视频讲解:https://www.bilibili.com/video/BV1oM411B7xq/
整体思路和打家劫舍第一步一样,区别就是如果是环形的话 那0位置的元素和最后位置的元素不能共存。
这里分2钟场景,一去掉头,二去掉尾,然后看一或者二求出来哪个更大。
整体方法一样,这里和自己的想法有区别的是自己的想法是做2个nums数组做截取得到[0,length - 1]和[1,length],而代码随想录里是用2个循环去处理。
初始化值的赋值方式和遍历循环i的初始值和结尾值没确定。
最终代码:
public int rob(int[] nums,int start,int end){
if (end == start) return nums[start];
int[] dp = new int[nums.length];
dp[start] = nums[start];
dp[start + 1] = Math.max(nums[start],nums[start + 1]);
for(int i = start + 2; i <= end; i++){
dp[i] = Math.max(dp[i-2] + nums[i],dp[i - 1]);
}
return dp[end];
}
337.打家劫舍III
文章讲解:https://programmercarl.com/0337.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8DIII.html
题目链接:https://leetcode.cn/problems/house-robber-iii/
视频讲解:https://www.bilibili.com/video/BV1H24y1Q7sY/
二叉树一样,只是把循环改成树的遍历了,根节点不能打劫左右子节点。
树的遍历有前中后。中为处理计算,前后为递归。
代码分成2种情况,偷父节点和不偷父节点。
暴力递归:
偷父节点则直接取父节点的值,不偷父节点取2个字节点的值的和。最终再使用Math.max把两种情况下的值做一个比较取大值。
动态规划:
使用一个长度为2的数组,0记录不选根节点的最大值,1记录选根节点的最大值。
树形dp,先搭后序遍历的框架,二叉树的递归需要回归下。
然后再在二叉树递归中增加动态规划逻辑。
public int rob(TreeNode root) {
// 二叉树一样,只是把循环改成树的遍历了,根节点不能打劫左右子节点
int[] result = backtracking(root);
return Math.max(result[0],result[1]);
}
public int[] backtracking(TreeNode node){
int[] dp = new int[2];
// 递归三部曲
// 终止条件
if(node == null){
return dp;
}
// 后序遍历
int[] left = backtracking(node.left);
int[] right = backtracking(node.right);
// 选当前节点和不选当前节点
// 选当前节点
dp[0] = node.val + left[1] + right[1];
// 不选当前节点
dp[1] = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);
return dp;
}
这节课整体用的时间不多,大概1.5h。
今天回顾了一下树的遍历,整体还是做递归遍历结合前、中、后序遍历去做逻辑。
学习了打家劫舍的解题方法,整体处理的逻辑还是讨论当前节点抢还是不抢。如果抢了当前节点,两个孩子就不能动,如果没抢当前节点,就可以考虑抢左右孩子(注意这里说的是“考虑”)