动态规划学习之LeetCode第198、213题
题目:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
要求:给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
分析:依然使用动态规划来解决这个问题:
1.定义一个数组dp,dp[i]表示当盗匪走到第i个房子的时候,此时安全盗取的最高金额;
2.寻找dp数组之间的关系式,盗匪走到第i个房子的时候,要决定是否盗取:
if dp[i-2] + nums[i] > dp[i-1],那么说明要盗取当前的第i个房子;
否则就选择不盗取当前这个第i和房子,使dp[i] = dp[i-1];
3.初始值就是,首先i要大于等于2,否则无法对关系式进行正确的运算,那么dp[0] = nums[0],dp[1] = max(nums[0],nums[1])
实现的代码如下:
public int rob(int[] nums) { if (nums.length==0) return 0; if (nums.length==1) return nums[0]; // dp[i]代表的就是当盗匪走到第i个房子的时候,此时安全盗取的最高金额 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++){ if (dp[i-2]+nums[i] > dp[i-1]) dp[i] = dp[i-2]+nums[i]; else { dp[i] = dp[i-1]; } } return dp[nums.length-1]; }
这里这个关系式可以简化成:dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]),至于这个关系式怎么保证盗贼不偷相邻的房子呢?就比如对于第i房子:
如果决定偷取,那么关系式是dp[i] = dp[i-2] + nums[i],即此时最高金额值等于从前i-2个房子偷取的最高金额加上当前第i个房子偷取的金额,第i-1个房子并没有包括进来,也就保证了不会偷相邻的房子;
如果不决定偷取,那么关系式就是dp[i] = dp[i-1],也就是不管你第i-1个房子到底偷取可没有,反正我第i个房子一定没有偷,所以就杜绝了可能相邻偷取的情况。
对于上面这个程序的空间复杂度是O(n),还可以进行优化,使空间复杂度变为o(1),即借助状态转移变量而不是状态转移数组,因为我们要的最终结果是一个值而不是一组值,优化的代码如下:
public int rob2(int[] nums) { if (nums.length==0) return 0; if (nums.length==1) return nums[0]; int[] dp = new int[nums.length]; int pre_2 = nums[0]; int pre_1 = Math.max(nums[0],nums[1]); for (int i = 2;i < nums.length;i++){ int cur = Math.max(pre_2+nums[i],pre_1); pre_2 = pre_1; pre_1 = cur; } return pre_1; }
题目:这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
分析:这个题目是上一个题目的“升级版”,也就是多了“首尾不能同时偷”这样一个要求,那我们此时来分析一下具体的情况,将一个大的问题拆分一下,盗贼偷取的情况分为一下三种:
1.第一间房被抢,最后一间房不能抢;
2.最后一间房被抢,第一间房不能抢;
3.第一间房和最后一间房都没有被抢;
这三种情况下,哪种情况的最终结果大,哪个就是最大值,但是我们还可以将这三种情况合并处理一下,我们将其合并为两种情况,即:
rob(nums,0,n-2):对于此种情况,就是将上述的第一种情况和第三种情况进行了合并,即从第一个房子开始到倒数第二个房子结束,既保证了不会抢最后一个房子,而对于第一个房子,既可能抢了也可能没抢,
rob(nums,1,n-1):这种情况是,将第二种情况和第三种情况进行了合并,从第二个房子开始到最后一个房子结束,既保证了不会抢第一个房子,而对于最后一个房子,可能抢了,也可能没抢。
记住,我们要求的是最优的策略,所以第一间房和最后一间房,抢不抢、抢哪个,根据的是累计的盗取金额值决定的,就比如 rob(nums,0,n-2),它从第一个房子开始,如果第一间房子抢了,说明它参与的累计值比第三种情况高的,如果第一间房子没有被抢,那么说明它参与的累计值没有第三种情况高,rob(nums,1,n-1)同理。
所以一定要分清楚这个题,并不是抢的房子数量越多钱就越多,而是在遵循题目条件的情况下,使得最终的累计值最高,代码如下所示:
public int rob(int[] nums) { if (nums.length == 0) return 0; if (nums.length == 1) return nums[0]; int n = nums.length; return Math.max(rob(nums,0,n-2),rob(nums,1,n-1)); } private int rob(int[] nums,int first,int last){ int pre_2= 0,pre_1=0; for (int i = first; i <= last; i++) { int cur = Math.max(pre_2+nums[i],pre_1); pre_2 = pre_1; pre_1 =cur; } return pre_1; }
题目来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber-ii
链接:https://leetcode-cn.com/problems/house-robber