你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
示例 1:
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 3:
输入:nums = [0]
输出:0
提示:
- 1 <=
nums.length
<= 100 - 0 <=
nums[i]
<= 1000
思路
这里先顺便贴一下198.打家劫舍的代码,本题的代码都是从那题的基础上而来:
class Solution {
public int rob(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
if(nums.length>=2) dp[1] = nums[1];
for(int i=2;i<nums.length;i++) {
int maxDP = -1;
for(int j=i-2;j>=0;j--) {
if(dp[j]>maxDP) maxDP = dp[j];
}
dp[i] = maxDP+nums[i];
}
int result = dp[0];
for(int i=1;i<dp.length;i++) {
if(dp[i]>result) result = dp[i];
}
return result;
}
}
先说一下198题
的想法,因为是线性的,所以DP的想法很简单:假设dp[x]
是从头开始一直偷到下标为x
处的屋子时,所能获得的最大金额。因此,只需要解出dp[]
数组之后,在这个数组中寻找到最大值,就是所求的答案。
这里要注意,因为偷了nums[0]
便不能偷nums[1]
,偷了nums[1]
便不能偷nums[0]
,因此,初始化时,需要初始化两个地方:dp[0] = nums[0], dp[1] = nums[1]
。
然后开始递推解dp[]
数组,从i=2
开始,每次都隔开前一个屋子(因为相邻屋子会报警)往前找已经解出的dp
最大值,然后再加上自己本身nums[i]
,就是当前的dp[i]
。比如求解dp[4]
时,要从dp[2]~dp[0]
里找出最大值,再加上nums[4]
,才是最后的dp[4]
,不能找dp[3]
。
再看这题,因为是环形,所以如果像198题
那样一次性走完整个数组的话,动态规划便会失去[无后效性]
,简言之,就是之前的选择会影响之后的结果。
比如对样例[200, 3, 140, 20, 10]
,如果按照198题
的思路来求解dp[]
数组,那么之前都是没问题的,解得:
dp[0] = 200
dp[1] = 3
dp[2] = 340
dp[3] = 220
一直到dp[4]
这最后一个位置,如果仍然是找之前dp[]
数组最大值加上本身的话,dp[4]
就会变成dp[2]+nums[4] = 340+10 = 350
,显然,这是不对的。为什么呢,因为dp[2]
这个状态,是选择了200
这个元素而来的,而因为213题
是个环形结构,当站在10
这个元素时,200
和20
显然是不可取的,这就失去了[无后效性]
。
【注】我个人理解的[无后效性]
就是指,我不管你选择什么路径走到dp[i]
这个状态,都不会影响后续状态dp[i+1]
的正确性,后续状态dp[i+1]
只跟前一个状态dp[i]
有关(有时候是前两个,比如斐波那契),跟再前面的状态就无关了。而像本题,因为dp[2]
选择了dp[0]
,从而影响了dp[4]
,dp[4]
受到了来自当初选择dp[0]
的无形影响,显然是不行的。
为了避免这个问题,我个人认为比较直观的做法是做两遍dp:
- 第一遍从
[0, 3]
,到dp[3]
就打住,这些结果一定是正确的(代码里我用了dp1[]
数组); - 第二遍从[
1, 4]
, 初始化dp[1]
和dp[2]
,从i=3
开始递推,这些结果也一定是无误的(代码里我用了dp2[]
数组)。
最后,将两个dp[]
数组整合到一起,重叠的相同位置上选择较大值,就是最终的dp[]
数组了。
代码
public class Solution {
public int rob(int[] nums) {
int i, j;
int[] dp1 = new int[nums.length];
int[] dp2 = new int[nums.length];
int[] dp = new int[nums.length];
//第一遍dp:[0, nums.length-2]
dp1[0] = nums[0];
if(nums.length>=2) dp1[1] = nums[1];
for(i=2;i<=nums.length-2;i++) {
int maxDP = -1;
for(j=i-2;j>=0;j--) {
if(dp1[j]>maxDP) maxDP = dp1[j];
}
dp1[i] = maxDP+nums[i];
}
//第二遍dp:[1, nums.length-1]
if(nums.length>=2) dp2[1] = nums[1];
if(nums.length>=3) dp2[2] = nums[2];
for(i=3;i<=nums.length-1;i++) {
int maxDP = -1;
for(j=i-2;j>=0;j--) {
if(dp2[j]>maxDP) maxDP = dp2[j];
}
dp2[i] = maxDP+nums[i];
}
for(i=0;i<dp.length;i++) {
if(dp1[i]>dp2[i]) dp[i] = dp1[i];
else dp[i] = dp2[i];
}
int result = dp[0];
for(i=0;i<dp.length;i++) {
if(dp[i]>result) result = dp[i];
}
return result;
}
}