题目
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
思路及题解
本题用动态规划求解,对于一个当前的房屋n,你可以选择的只有偷或不偷两个选项。偷的话那么你之前只能偷n-2的房屋,不偷的话,你就可以偷n-1的房屋。若以memo[n]来表示第n个房屋时,则有memo[i] = max(memo[i-2] + nums[i], memo[i-1])
。
代码如下:
class Solution:
def rob(self, nums: 'List[int]') -> 'int':
if len(nums) == 0:
return 0
if len(nums) == 1:
return nums[0]
memo = [-1] * len(nums)
#print(memo, memo[0])
memo[0] = nums[0]
memo[1] = max(nums[0], nums[1])
for i in range(2,len(nums)):
memo[i] = max(memo[i-2] + nums[i], memo[i-1])
return memo[len(nums)-1]
贴上讨论区的高评思路:
This particular problem and most of others can be approached using the following sequence:
- Find recursive relation
- Recursive (top-down)
- Recursive + memo
- (top-down) Iterative + memo (bottom-up)
- Iterative + N variables (bottom-up)
Step 1. Figure out recursive relation.
A robber has 2 options: a) rob current house i; b) don’t rob current house.
If an option “a” is selected it means she can’t rob previous i-1 house but can safely proceed to the one before previous i-2 and gets all cumulative loot that follows.
If an option “b” is selected the robber gets all the possible loot from robbery of i-1 and all the following buildings.
So it boils down to calculating what is more profitable:
robbery of current house + loot from houses before the previous
loot from the previous house robbery and any loot captured before that
rob(i) = Math.max( rob(i - 2) + currentHouseValue, rob(i - 1) )
Step 2. Recursive (top-down)
Converting the recurrent relation from Step 1 shound’t be very hard.
public int rob(int[] nums) {
return rob(nums, nums.length - 1);
}
private int rob(int[] nums, int i) {
if (i < 0) {
return 0;
}
return Math.max(rob(nums, i - 2) + nums[i], rob(nums, i - 1));
}
This algorithm will process the same i multiple times and it needs improvement. Time complexity: [to fill]
Step 3. Recursive + memo (top-down).
int[] memo;
public int rob(int[] nums) {
memo = new int[nums.length + 1];
Arrays.fill(memo, -1);
return rob(nums, nums.length - 1);
}
private int rob(int[] nums, int i) {
if (i < 0) {
return 0;
}
if (memo[i] >= 0) {
return memo[i];
}
int result = Math.max(rob(nums, i - 2) + nums[i], rob(nums, i - 1));
memo[i] = result;
return result;
}
Much better, this should run in O(n) time. Space complexity is O(n) as well, because of the recursion stack, let’s try to get rid of it.
Step 4. Iterative + memo (bottom-up)
public int rob(int[] nums) {
if (nums.length == 0) return 0;
int[] memo = new int[nums.length + 1];
memo[0] = 0;
memo[1] = nums[0];
for (int i = 1; i < nums.length; i++) {
int val = nums[i];
memo[i+1] = Math.max(memo[i], memo[i-1] + val);
}
return memo[nums.length];
}
Step 5. Iterative + 2 variables (bottom-up)
We can notice that in the previous step we use only memo[i] and memo[i-1], so going just 2 steps back. We can hold them in 2 variables instead. This optimization is met in Fibonacci sequence creation and some other problems [to paste links].
/* the order is: prev2, prev1, num */
public int rob(int[] nums) {
if (nums.length == 0) return 0;
int prev1 = 0;
int prev2 = 0;
for (int num : nums) {
int tmp = prev1;
prev1 = Math.max(prev2 + num, prev1);
prev2 = tmp;
}
return prev1;
}