leetcode 198 打家劫舍 (一维动态规划、基础)

转自:https://mp.weixin.qq.com/s?src=11×tamp=1528981756&ver=938&signature=WYEnOuwpBwSb-oFva2oX1M7Y-6ATjMYgPp3E-FtFu8gjBLCNcuiAC4B5DNjgRFKGKkFAbZSRzWO*cD*k9o7G5tp5DUvl3*F-zy9ua6U9JgGoAluGKRMBNpARO2*-rtUl&new=1

转载: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];  
    }  
};   

通常利用动态规划的共性:

本质:递归



 


猜你喜欢

转载自blog.csdn.net/weixin_41413441/article/details/80698363