198. 打家劫舍
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例1:
输入: [ 1 , 2 , 3 , 1 ] [1,2,3,1] [1,2,3,1]
输出: 4 4 4
示例2:
输入: [ 2 , 7 , 9 , 3 , 1 ] [2,7,9,3,1] [2,7,9,3,1]
输出: 12 12 12
思路
本题看题能看出基本的动态规划思路,即,是偷本房屋的还是偷上一个房屋的。
dp[i] = max(dp[i-2]+nums[i],dp[i-1])
解法
class Solution {public int rob(int[] nums) {if(nums == null || nums.length == 0){return 0;}if(nums.length == 1){return nums[0];}int[] dp = new int[nums.length];dp[0] = nums[0];dp[1] = Math.max(dp[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];}
}
总结
打家劫舍是动态规划的一个经典应用,重在能够加深对于动态规划数组的理解。
213. 打家劫舍Ⅱ
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
示例1:
输入: n u m s = [ 2 , 3 , 2 ] nums = [2,3,2] nums=[2,3,2]
输出: 3 3 3
示例2:
输入: n u m s = [ 1 , 2 , 3 , 1 ] nums = [1,2,3,1] nums=[1,2,3,1]
输出: 4 4 4
示例3:
输入: n u m s = [ 1 , 2 , 3 ] nums = [1,2,3] nums=[1,2,3]
输出: 3 3 3
思路
本题是打家劫舍的2.0版本,主要的改动在于,把之前排成一排的房子变成了环形,那么其实本质上的动态规划策略是不变的,只是因为房屋排列方式的变化而需要我们增加一些限制的条件。
而这里需要考虑的就是首尾元素要不要取,取谁。根据此可以分为不取首和不取尾两种情况。
解法
class Solution {public int rob(int[] nums) {if(nums == null || nums.length == 0){return 0;}int len = nums.length;if(len == 1){return nums[0];}return Math.max(helper(nums,0,len-1),helper(nums,1,len));}public int helper(int[] nums,int start,int end){int x = 0,y = 0,z = 0;for(int i = start;i<end;i++){y = z;z = Math.max(y,x+nums[i]);x = y;}return z;}
}
总结
是打家劫舍的升级版,也是对其的另一种应用。重点在于能够看出环结构房屋的限制需要体现在动态规划之外的限制中。
337. 打家劫舍Ⅲ
题目描述
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
示例1:
输入: r o o t = [ 3 , 2 , 3 , n u l l , 3 , n u l l , 1 ] root = [3,2,3,null,3,null,1] root=[3,2,3,null,3,null,1]
输出: 7 7 7
示例2:
输入: r o o t = [ 3 , 4 , 5 , 1 , 3 , n u l l , 1 ] root = [3,4,5,1,3,null,1] root=[3,4,5,1,3,null,1]
输出: 9 9 9
思路
我真的觉得,第一次见这种树上动态规划时,它是值得困难标签的。懵掉,直接上答案吧。
本题的难点主要在于,要将树遍历的递归和动态规划相结合,这个怎么结合就很有说法。
1、确定递归函数:
dp数组及其下标的含义在于:下标为0记录不偷该节点所得到的最大金钱,下标为1记录偷该节点所得到的最大金钱,故而dp数组为一个长度为2的数组。
这里需要提出的也是我之前的一个困惑点,就是长度为2的数组其实是无法标记树中每个节点的状态的。这里其实是因为,确实不用,递归过程中系统栈实惠保存每一层的递归参数的,或者说,递归过程中,想得到参数是可以调用的。
2、确定终止条件
遇到空节点,返回
3、确定遍历顺序
树的后序遍历,因为需要通过递归函数的返回值来做下一步的计算
递归左孩子得到左孩子偷与不偷的金钱
递归右孩子得到右孩子偷与不偷的金钱
4、确定单层递归的逻辑
如果偷当前节点,那么左右孩子就不能偷。
如果不偷当前节点,那么左右孩子就可以偷。
最后计算最大金钱。
解法
/*** 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) {int[] res = robAction(root);return Math.max(res[0],res[1]);}public int[] robAction(TreeNode root){int[] res = new int[2];if(root == null){return res;}int[] left = robAction(root.left);int[] right = robAction(root.right);res[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);res[1] = root.val+left[0]+right[0];return res;}
}
总结
本题其实也可以纯粹后序遍历,也能做,但是超时了。
还可以通过遍历过程中记录中间结果,但都没有动态规划简单,或者说是,代码上的简单。
答案中提到本题算是树形动态规划的入门题,这守门员属实是不简单啊。