转载:https://blog.csdn.net/fisherming/article/details/79809071
题目:
你是一个职业盗贼,锁定了一条大街准备今晚作案,街上每栋房子里都有固定量的财务。但是相邻的房子之间有报警器,一旦两个相邻的房子在同一晚被盗,就会触发报警器。现已知一系列非负整数表示每个房子里的财务数目,请计算在不触发报警器的情况下,你今晚可以盗取的最大值。
思路:
因为我可以选择偷或者不偷,所以我们会有两个分条件。假设共有四个房子ABCD。那么我们会有以下两个分选择:
于是我们可以知道,虽然我们开始想要得到在ABCD中进偷窃的最大收益,但是现在这个问题变成了两个子问题:
计算在CD中的最大收益,然后加上A房子的收益
计算在BCD中的最大收益
然后,我们比较以上两个选项,就能知道在ABCD中选择的最大收益。
针对在BCD中选择房子,获得最大收益的问题,我们可以同样的进行拆解。
这时候,我们发现 计算在CD中选择的最大收益。这个问题在ABCD的时候也出现了。我们可以重新计算一遍,但是也可以把之前的答案保存下来,这样就不需要计算了。
那么我们需要保存的答案都有哪些呢?ABCD、BCD、CD、D。也就是从一个节点到终点的情况。于是我们可以构建数组memo[x],其中的x表示的是剩余元素的个数。
于是上述的四种情况,分别对应memo[4]、memo[3]、memo[2]、memo[1]。
因此,我们的目标是memo[4],它的结果可以拆解为两个子问题:
memo[2] + Value_A
memo[3]
同理,当我们计算memo[3]的时候,它的结果可以拆解为两个子问题:
memo[2] + Value_B
memo[1]
然后选出这两种情况的最大值,递归执行,直到index<0。
public int solve(int index, int[] nums){
if(index < 0){
return 0;
}
int max = Math.max(nums[index] + solve(index - 2, nums), solve(index - 1, nums));
return max;
}
public int rob(int[] nums) {
return solve(nums.length-1, nums);
}
此种暴力方法在执行第56个测试用例时,超出时间限制。
假设我们抢n-1家,那么接下来的执行方案:
n-1 ->(n-3, n-4, n-5)
假设我们抢n-2家,那么接下来的方案为:
n-2 ->(n-4, n-5)
那么我的两种决策方式只是影响能不能抢n-3,在n-3之后都是随便抢的;通过观察上述两种方案,我们发现了n-4,n-5被重复计算。因此,每一家都有两种可能,抢或者不抢。则该算法的时间复杂度为:O(2n)。
为了避免上述的重复计算(优化为动态规划方法),我们初始化一个数组来记录此下标是否被计算过,将数组初始化为-1,如果当前index被算过,就记录下来。
如果这个index被计算过,那么我们直接返回这个index对应的值即可,这就是去冗余,采用空间换时间的方法。因此当n-1房屋的最优解算过后,就能推导出n房屋的最优解。这就是动态规划的思想。
因此,我们考虑使用动态规划,设置result[]数组记录抢夺该房屋可能的最大收益,同时用来记录此房屋是否被抢过。自顶向下解法
class Solution {
public static int[] result;
public int solve(int index, int[] nums){
if(index < 0){
return 0;
}
if(result[index] >= 0){
return result[index];
}
result[index] = Math.max(nums[index] + solve(index-2 , nums), solve(index-1, nums));
return result[index];
}
public int rob(int[] nums) {
result = new int[nums.length];
for(int i=0; i < result.length; i++){
result[i]=-1;
}
return solve(nums.length-1, nums);
}
}
对于每个房屋我们都算了一次,那么时间复杂度为O(n)
自底向上解法
public int rob(int[] nums) {
if (nums.length == 0){
return 0;
}
if (nums.length == 1){
return nums[0];
}
if (nums.length == 2){
return Math.max(nums[0], nums[1]);
}
int[] result = new int[nums.length];
result[0] = nums[0];//只有一家的情况下,只能抢这一家了
result[1] = Math.max(nums[0], nums[1]);//有两家可选的情况下,抢价值最大的那一家
for(int index=2; index < result.length; index++){
result[index] = Math.max(nums[index] + result[index-2], result[index -1]);
}
return result[nums.length -1];
}
}
自底向上,编码简单,递推(自顶向下为递归)。既然自底向上不需要递归,那么就不需要solve函数了。我们只要处理好边界条件,然后计算即可。
C++代码:
非常简洁:
使用dp[i]表示到第i个房间时得到的最大金额数,得到状态转移方程:
dp[i]=max{dp[i-1],dp[i-2]+money[i]};
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()==0) return 0;
int i,dp[100000];
dp[0]=nums[0]; //必须先初始化前两个值
dp[1]=nums[0]>nums[1]?nums[0]:nums[1];
for(i=2;i<nums.size();i++){
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.size()-1];
}
};
通常利用动态规划的共性:
本质:递归