You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
Credits:
Special thanks to @ifanchu for adding this problem and creating all test cases. Also thanks to @ts for adding additional test cases.
【题目分析】
这是一个很经典的动态规划的题目,意思是一个盗贼要偷盗一条街道上的店铺。唯一的限制就是不能同时偷盗连续的两家店铺,否则的话报警器就会被触发,求在不触发警报的情况下该盗贼可偷盗的最多金钱数。
【思路】
1. 用递归来解决
盗贼遍历店铺,从当前店铺开始获取的最大收益 = max(偷盗当前店铺获取的最大收益, 不偷盗当前店铺获取的最大收益)
代码如下:
1 public class Solution { 2 public int rob(int[] nums) { 3 return robber(nums, 0); 4 } 5 6 public int robber(int[] nums, int start) { 7 if(start > nums.length-1) return 0; 8 if(start == nums.length-1) return nums[start]; 9 int result1 = 0, result2 = 0; 10 result1 = nums[start] + robber(nums, start+2); 11 result2 = robber(nums, start+1); 12 13 return result1 > result2 ? result1 : result2; 14 } 15 }
上面的代码时间复杂度较高。原因在于递归的时候一些中间的计算结果没有保存下来,导致多次计算已经计算后的结果。对上面的代码进行改进如下:
1 public class Solution { 2 public int rob(int[] nums) { 3 int[] mark = new int[nums.length]; 4 for(int i = 0; i < nums.length; i++) { 5 mark[i] = -1; 6 } 7 return robber(nums, mark, 0); 8 } 9 10 public int robber(int[] nums, int[] mark, int start) { 11 if(start > nums.length-1) return 0; 12 if(start == nums.length-1) return nums[start]; 13 int result1 = 0, result2 = 0; 14 if(start+2 < nums.length && mark[start+2] > -1) { 15 result1 = nums[start] + mark[start+2]; 16 } else { 17 result1 = nums[start] + robber(nums, mark, start+2); 18 } 19 20 if(start+1 < nums.length && mark[start+1] > -1) { 21 result2 = mark[start+1]; 22 } else { 23 result2 = robber(nums, mark, start+1); 24 } 25 26 mark[start] = result1 > result2 ? result1 : result2; 27 return mark[start]; 28 } 29 }
上面的代码用mark数组保存了已经计算过的最优值,递归过程中如果发现该值已经被计算过,则直接取出已经计算过的结果即可。
递归表达式为:rob[start] = max(rob[start+1], nums[start] + rob[start+2])
上面的代码太过冗余,优化后的代码如下:
1 public class Solution { 2 public int rob(int[] nums) { 3 int len = nums.length; 4 if(len == 0) return 0; 5 if(len == 1) return nums[0]; 6 7 int[] mark = new int[len]; 8 mark[len-1] = nums[len-1]; 9 mark[len-2] = Math.max(nums[len-1], nums[len-2]); 10 11 for(int i = len-3; i >= 0; i--) { 12 mark[i] = Math.max(mark[i+1], nums[i] + mark[i+2]); 13 } 14 return mark[0]; 15 } 16 }
上面代码的空间复杂度为O(n),我们发现其实不必把已经计算过的所有信息保存下来,只要保存后两个记录即可,优化后的空间复杂度为O(1),代码如下:
1 public class Solution { 2 public int rob(int[] nums) { 3 int len = nums.length; 4 if(len == 0) return 0; 5 if(len == 1) return nums[0]; 6 7 int post2 = nums[len-1]; 8 int post1 = Math.max(nums[len-2], nums[len-1]); 9 10 for(int i = len-3; i >= 0; i--) { 11 int temp = post1; 12 post1 = Math.max(post1, nums[i] + post2); 13 post2 = temp; 14 } 15 return post1; 16 } 17 }