题目描述:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。
示例如下:
输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
然后呢,其实这个题是个动态规划的题,动态规划我没看过其实,然后结合题目简单对比了一下,再加上抄了一下答案,有了点想法,记录一下。
动态规划的概念就不写了,
首先是拆分问题,我的理解就是根据问题的可能性把问题划分成一步一步这样就可以通过递推或者递归来实现.
关键就是这个步骤,动态规划有一类问题就是从后往前推到,有时候我们很容易知道:如果只有一种情况时,最佳的选择应该怎么做.然后根据这个最佳选择往前一步推导,得到前一步的最佳选择
然后就是定义问题状态和状态之间的关系,我的理解是前面拆分的步骤之间的关系,用一种量化的形式表现出来,类似于高中学的推导公式,因为这种式子很容易用程序写出来,也可以说对程序比较亲和(也就是最后所说的状态转移方程式)
动态规划的核心好像就是找到一个状态方程。
首先如果采用递归的方式的话,会重复计算多个结果,影响速度,可以将每次的计算结果保存下来,然后在使用的时候直接调用就会节省很多时间。
其实我这个想法是官方题解的,照着想的
设一个数组dp[n]
保存每一次的步骤,如这个题中dp[n]
保存从前n个房间中偷到的最多的钱。
此时再多偷第n+1
个房间的时候,有两种情况
- 第
n+1
个房间不偷,因此第n个房间会被偷,所以dp[n+1]=dp[n]
- 第
n+1
个房间偷,因此第n个房间不会被偷,所以dp[n+1]=dp[n-1]+num
,num是第n+1
然后呢,第一种情况里,如果第n个不会被偷的话,则n-1个房间被偷,则dp[n]=dp[n-1]
,即第n+1个房间会被偷,dp[n+1]=dp[n-1]+num
,也就是第二种情况了。
然后可以得到一个状态方程吧
dp[n+1]=max(dp[n],dp[n-1]+num)
。
然后就可以写代码了。
static int rob(int[] nums) {
int Result=0;
if (nums.length==1)
return nums[0];
else if (nums.length==2)
return Math.max(nums[0],nums[1]);
//因为第一家和最后一家不能一起偷,所以范围是第一个到倒数第二个、第2个到倒数第一个
return Math.max(s(Arrays.copyOfRange(nums, 0, nums.length-1)),s(Arrays.copyOfRange(nums, 1, nums.length)));
}
static int s(int[] nums){
int Result=0;
int i=0;
int n=0,n_1=0,temp=0;
for (i=0;i<nums.length;i++){
temp=n;
n=Math.max(n_1+nums[i], n);
n_1=temp;
}
return n;
}
首先有两个特殊情况,当长度为1的时候,即只有一个房间,肯定会被偷。如果有两个房间,则会偷最大的一个房间。
然后呢,有两个范围,一个是[0,n-1],另一个是[1,n]。也就是第一个房间和最后一个房间只有一个被偷的两个情况。
然后在s()
方法函数中,就按照状态方程来写代码。
有几个变量,主要n表示dp[n]
,n_1表示dp[n-1]
,首先需要遍历数组,然后
n=Math.max(n_1+nums[i], n);
也就是dp[n+1]=max(dp[n],dp[n-1]+num)
。
这里左边的n和右边的n不一样,左边的n实质上是dp[n+1]
。
然后因此需要一个变量来记住dp[n-1]
。就用一个temp
记住吧。
然后就引出了temp=n和n_1=temp
,第一个是记录n-1的值,在max以后赋值给真正的n_1变量,完成了转化。