LeetCode——213 打家劫舍 II(JAVA)

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 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这个元素时,20020显然是不可取的,这就失去了[无后效性]

【注】我个人理解的[无后效性]就是指,我不管你选择什么路径走到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;
	}
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值