1.题目链接:198. 打家劫舍
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
解法:
1.五部曲:
①首先此题判断哪家可以不可以偷是根据前面的状态来判断的,所以存在状态转移,用动态规划来做。
②确定dp[i]的含义,即考虑包括下标i和i之前的位置能偷窃到的最高金额为dp[i]。
③递推公式:dp[i] = ? 可以分为两种情况即偷与不偷。如果偷的话 = dp[i-2] + nums[i].如果不偷的话 = dp[i-1]。故递推公式dp[i] = max(dp[i-1], dp[i-2] + nums[i])
④初始化:因为根据递推公式要根据前两个值来求,所以初始化dp[0] = nums[0], dp[1] = max(nums[0], nums[1])。其余位置的初始化任何都可以,因为当前位置的值只通过前面的值推导出来的,所以其余的值无所谓。
⑤遍历顺序:因为是根据前面的值推导的,所以遍历顺序从小到大。
⑥最后返回dp[nums.length-1];
2.步骤:
①if nums.length == 1,返回nums[0]。---如果没有这部的话,在初始化数组的时候会越界。
②创建dp数组,长度为nums.length
③初始化:dp[0] = nums[0] ,dp[1] = max(nums[0], nums[1]);
④遍历:for(i = 2~nums.length){dp[i] = max(dp[i-2] + nums[i], dp[i-1]);}
⑤最后return dp[nums.length-1]
下面为代码(java):
2.题目链接:213. 打家劫舍 II
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
解法:
1.五步曲:
①此题和打家劫舍I的区别就是,这次的房屋是个环形了,应该怎么处理?
②可以分三种情况考虑:
1)不考虑头和尾,就是打家劫舍问题。
2)考虑头不考虑尾,就是打家劫舍问题。
3)考虑尾不考虑头,就是打家劫舍问题。
4)同时第一种情况被第二和第三种都包含,所以一共就是两种情况最后取最大值即可。
2.步骤:
①在主函数中:if(nums.length == 1){return nums[0];}
②然后写一个函数实现打家劫舍:
1)此时我们可以优化代码:即就相当于维护三个变量,即相邻的三个数。有点像斐波那契数列的优化。
2)参数就是数组,start 和 end;返回值类型就是int型
即定义 x = 0 --- 表示第一个数的值;定义y = 0 --- 表示第二个数的值;定义z = 0 --- 表示第三个数的值。
3)遍历:for(i = start;i < end;i++ ){
y = z;
z = Math.max(y, x + nums[i]);
x = y;}
4)可能会有点疑惑,其实第一步和第二步就是求length = 1和2的结果,第二步的时候完成了dp[0]的初始化。第三部开始完成了dp[1]的初始化,然后开始计算dp[i]。
5)最后返回z
6)最后在主函数中调用helper(nums, 0, nums.length-1)和helper(nums, 1, nums.length)并将他们俩进行比较即取最大值,返回最大值即可。
下面为代码(java):
3.题目链接:337. 打家劫舍 III
题目描述:
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额
解法:
1.五步曲:
①此题就是将可偷的地区变成了一颗树型。那么我们要考虑树就要考虑递归三步。
②同时这是状态转移的题,所以要考虑动态规划,故交叉考虑即可。
③首先确定dp[]数组,即dp数组是个一维数组,且只有两个元素,dp[0]表示的是当前节点不偷了获得的最高金额,dp[1]表示的是当前节点偷了获得的最高金额。即递归函数的返回值就是int[],参数就是TreeNode.
④确定递推公式:dp[0] = max(leftdp[0], leftdp[1]) + max(rightdp[0], rightdp[1]);
dp[1] = cur.val + leftdp[0] + rightdp[0];
⑤根据递推公式可以知道,我们要根据左右孩子的状态来确定当前节点的状态,所以遍历顺序就是左右中。
⑥终止条件? 当遍历到空节点的时候,return{0,0};
⑦最后返回的是就是根节点处的状态值数组,因为整个树是从下到上去反馈结果的,所以最后return dp;
2.步骤:
①写一个递归函数,参数 TreeNode cur,返回值类型 int[]{
②创建dp[]= new int[2] --- 即长度为2的数组
③if(cur == null){return dp;} --- 就相当于返回了{0,0},因为根本没有进行修改初始值就是0.
④按左右中的顺序遍历即:
int[] leftdp = helper(cur.left);
int[] rightdp = helper(cur.right);
dp[0] = max(leftdp[0], leftdp[1]) + max(rightdp[0], rightdp[1]);
dp[1] = cur.val + leftdp[0] + rightdp[0];
⑤最后返回dp。然后在主函数中用result数组承接递归函数的返回值,再return max(result[0], result[1])就是最终结果。
下面为代码(java):
4.总结:
①三题都是抢劫问题,都是相邻的不能抢,不同的就是地方的布局不同,一个是线性的,一个是环型的,一个是树型的。
②对于线性和环形问题:当前获得的钱数根据相邻的前两个来确定递推公式。而环形问题可以拆成线性问题来解决。对于树形问题:当前获得的最大钱数根据左右孩子的状态来获得。