LeetCode:198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 :
输入:[2,1,1,2]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 4 号房屋 (金额 = 2)。
偷窃到的最高金额 = 2 + 2 = 4 。
这个题目一看起来,就很像动态规划,那么从动态规划分析。
动态规划的的四个解题步骤是:
1.定义子问题
2.写出子问题的递推关系
3.确定 DP 数组的计算顺序
4.空间优化(可选)
那么这道题的题解就按照步骤一步一步来:
1.子问题便是将原题的大问题,找出其中满足规律的最小问题。从大问题f(n)中找出f(k),其中k是刚好满足规律的最小值,这个题的k很明显就是2。
2.找出递推关系,这是一个比较简单的数学规律题,我们可以得知:f(k) = max{f(k-1),f(k-2)+nums[k]}。
3.动态规划有两种计算顺序,一种是自顶向下的、使用备忘录的递归方法,一种是自底向上的、使用 dp 数组的循环方法。
绝大部分情况下,我们都是采用dp 数组的循环方法,这个题也不例外。对于此题:发现每个 f(k) 依赖 f(k-1) 和 f(k-2)。
进行到第三步,已经可以开始代码了。
class Solution { public int rob(int[] nums) { //当数组长度为1则返回这个数,数组长度为2则返回最大的那个数。 if(nums.length==1){ return nums[0]; }else if(nums.length==2){ return Math.max(nums[0],nums[1]); } //用dp[nums.length]保存每个下标的最大值,数组长度大于2后,则有:dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]) 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] = dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]); } return dp[nums.length-1]; } }
仔细观察这个题,我们需要的结果只是一个数字,但是空间复杂度却是O(n),所以肯定还有空间优化的可能,那么进行第四步。
4.空间优化的基本原理是,很多时候我们并不需要始终持有全部的 DP 数组。对于此题,最后一步计算 f(n) 的时候,实际上只用到了 f(n-1) 和 f(n-2) 的结果。n-3 之前的子问题,实际上早就已经用不到了。
那么,我们可以只用两个变量保存两个子问题的结果,就可以依次计算出所有的子问题。
class Solution { public int rob(int[] nums) { //当数组长度为1则返回这个数,数组长度为2则返回最大的那个数。 if(nums.length==1){ return nums[0]; }else if(nums.length==2){ return Math.max(nums[0],nums[1]); } //用curr表示当前值,prev表示前一个值。 int prev = nums[0]; int curr = Math.max(nums[0],nums[1]); for(int i = 2;i<nums.length;i++){ int temp = Math.max(curr,prev+nums[i]); prev = curr; curr = temp; } return curr; } }