LeetCode 198、House Robber

189 篇文章 0 订阅
162 篇文章 0 订阅

 

 

 

首先,对于这种问题,如果没有思路的话,就想一下它的暴力解法。在这个街的所有房子都可以偷,最终是选择几个房子进行偷取,那么是在这n个房子中的一个组合,对于这n个房子一共有多少个组合呢?一共有2^n个组合,对于每一个组合,首先要做的就是检查这个组合有没有相邻的房子,如果有相同的房子会触发报警系统,所以,不在考察的范围里。其次,它没有相邻的房子的话,那么它就有可能是我所需要的一个解,那么就可以记录这样的一个价值,最终找到最大值。那么对于每个组合检查是否有相邻的房子,以及这个组合对应的价值是多少,这个过程是一个O(n)的过程。一共有2^n这种组合。所以,这种暴力解法一共是O((2^n)*n)完成的。所以这种暴力解法一共是O((2^n)*n)的时间复杂度。虽然这个暴力解法耗时会非常多,但是这个暴力解法是之前回溯方法时讲过的问题。

 

但是这个问题,并不是让你求所有的组合,而是求一个最大值。换句话说,它是一个最优化的问题,对于这类问题,它的解空间又是一个组合的解空间,很容易地想到要使用递归的方式来解决这个问题,那么,会不会最终找到的结果中会有重叠子问题和最优子结构这样的性质,进而就可以使用记忆化搜索和动态规划来解决这样的问题。那么,进一步来分析一下,如果我们不是检查所有的组合,可以使用怎样的方法找到满足条件的,能够偷取宝物又不触发报警系统这样的情况。

这个问题是偷取[0...n-1]范围内所有房子的宝物,

 

那么可以考虑偷取0这个房子,或者也可以考虑不偷取0偷取1这个房子,或者0、1都不偷取偷取2这个房子,依次类推,最后一个选择是只偷取n-1这个房子。那么当我们做出了这样一个选择之后,进一步我们后续能够偷取哪些房子也就固定下来了,比如,最初我们选择了偷取0号房子,那么现在的子问题就是考虑偷取[2...n-1]这个编号的房子的宝物。这里把1隔去了,是因为1和0是相邻的,既然已经决定了偷取0那么就不能偷取1了,要不然就会触发警报系统。所以,从2开始考虑。同理,如果决定了偷取1,换句话说不去偷取0的话,下面的任务就是考虑偷取从3一直到n-1所有的房子。依次类推,最后一个选择是偷取n-1个房子,而且,前面的所有房子都不去偷取,那么在这种情况下,我们也不用去考虑其它的房子了,后续的房子是在这个空集。

 

 

如果偷取“考虑[2....n-]”这个房子的话,那么此时的选择就是这样的。可以偷取2号房子,剩下的问题就是考虑偷取[4...n-1]这个房子,或者不偷取2号房子,偷取3号房子,之后考虑偷取[5...n-1]这个房子,依次类推。

 

如果在这棵树上,直接考虑偷取1号房子,而不考虑偷取[3...n-1]这个房子的话,那么对于“考虑[3...n-1]”这个房子,接下来就是偷取3号房子和"考虑[5...n-1]"房子或者偷取4号房子和"考虑[6...n-1]"这些房子。在这棵递归树种,存在着很多重叠子问题。与此同时,每一个问题其实都是在求解一个最优化的值。

那么子问题的最优化的值,再配合上当前的决策,在每一步中选择一个最大值,就能得到原问题的最优解,相当于它也有这样的最优子结构的性质。所以在解决了这个递归算法后,就可以使用记忆化搜索来解决,或者可以用动态规划的方式来解决。

 

对该问题进行更加形式化的分析。

在之前的递归树中,每一个节点表达的是要解决一个问题,是什么问题呢?是考虑偷取从编号x到编号n-1这个范围里的房子。那么,每一个节点表达的要解决的这个问题,在动态规划里通常叫定义了一个状态。状态这个词听起来非常抽象,但是可以理解成对递归函数的定义。之前在定义递归函数的时候也强调过,定义函数的时候一定要明确清楚它的含义。比如说,对于这个问题,真正定义的这个状态是"考虑偷取从[x...n-1]范围里的房子",但是,这并不代表我一定会去偷取x范围这个房子,这个x只是考虑的范围的一个起点,在有些动态规划中,这个定义有可能就被定义成了“一定要偷取x范围的这个房子”,这两个状态是不一样的。可以理解成,这两个函数,它本身的定义就是不一样的。这会影响后续书写的逻辑。

 

 

当完成了一个状态的定义之后,之后的一件事就是定义一个状态的转移。

下面这个式子就是这个问题的状态转移。

设置了一个函数叫做f,f中的参数实际上就是在状态定义中的x,初始化的时候,整个状态其实就是在求f(0),换句化话说,就是考虑偷取从0到n-1这个范围里的所有的房子,我们能获取到的最大值是多少。为了获得最大值,我们就需要考虑这些情况,用max在以下的情况中求最大值。在哪些范围内求最大值呢?V(0)+f(2), V(0)就是取出0号房子它所代表的价值,之后再加上f(2)从2到n-1这个范围里所有的房子,这是一种可能,或者V(1)+f(3),也就是决定了去偷取1号房子,0号房子不动,之后去考虑从3一直到n-1这个范围里的所有房子,依次类推,或者是从V(2)+f(4),就是0、1这两个房子都不动,偷取2号这个房子,同时继续考虑偷取从4一直到n-1这个范围里的所有的房子,.........,一直到最后,决定偷取V(n-3)+f(n-1),即偷取n-3号房子里的宝物,再加上f(n-1)就是"考虑偷取[n-1...n-1]"这个范围里的所有的房子。当然,在这个定义下,f(n-1)考虑的房子其实只有1个。剩下的还有一个V(n-2)可以想象成加上f(n),但是对于f(n)来说就是“考虑偷取[n....n-1]范围里的房子”,n比n-1还大,这是一个空集,这个范围没有意义,这里直接写成了V(n-2)以及V(n-1),就是直接去偷取编号为n-1的这个房子。那么后面也没有房子了。在这么多的决策中,我选出一个最大值,这个方程就叫做状态转移方程。这里,这个方程写的是f(0)等于这个方程,可以更加抽象地写为f(x)。实际上,递归函数就是在实现这样的一个状态转移方程。状态实际上就是定义了这个函数要做什么,而状态转移定义了这个函数怎么做。

 

状态定义不同,也可以得到相同的解:

 

 

 

记忆化搜索:

class Solution {
    //memo[i]表示考虑抢劫nums[i...nums.length-1]所能获得的最大收益
    int[] memo;
    public int rob(int[] nums) {
        if(nums.length<=0)
            return 0;
        memo=new int[nums.length];
        //传入的数组的值可能都为0,int数组初始化值都为0,不能根据memo[i]是否为0进行计算,如果不赋初值-1,那么memo记忆化搜索失效,内存有可能溢出
        for(int i=0;i<nums.length;i++){
            memo[i]=-1;
        }
        return tryRob(nums,0);
    }
    
    //考虑抢劫[index...nums.length-1]这个范围的所有房子
    public int tryRob(int[]nums,int index){
        if(index>nums.length-1)
            return 0;
        if(memo[index]!=-1)
            return memo[index];
        int res=-1;
        for(int i=index;i<=nums.length-1;i++){
            res=Math.max(res,nums[i]+tryRob(nums,i+2));
        }
        memo[index]=res;
        return res;
    }
}

 

 

动态规划法:

class Solution {
    int[]memo;
    
    public int rob(int[] nums) {
        int n=nums.length;
        if(n<1)
            return 0;
        memo=new int[nums.length];
        memo[n-1]=nums[n-1];
       //memo[i]表示考虑抢劫nums[i...n-1]所能获得的最大收益
        for(int i=n-2;i>=0;i--){
            // memo[i]
            for(int j=i;j<n;j++){
                memo[i]=Math.max(memo[i] ,nums[j]+(j+2<n?memo[j+2]:0));
            }
        }
       return memo[0];
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值