算法训练#3:打家劫舍

45 阅读4分钟

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

第一题

198. 打家劫舍 image.png

思路方法

很显然可以用动态规划方法的标准流程来做这道题目

最后一步

这里的最后一步肯定是按照规则对一定数量的房间行窃后得到的总额,那么可以根据这个为dp数组定义,则数组dpdp[i] 表示在第1间到第i+1间房间中小偷行窃后得到的最高总额

转移方程

假设有n间房屋,那么我们最后一步得到的值则为dp[n-1],若房屋多一间,也就是一共有n+1间房屋,我们则需要求dp[n]
按题目对规则的定义,我们可以知道若需要偷第n+1间屋子的话那么第n间屋子必然是偷不了的,因为不知道dp[n] 取的值所代表的行窃计划会不会偷到第n间房子。
所以,我们要看dp[n-2](在第1间到第n-1间中小偷行窃后得到的最高金额)这个必然不会偷到第n间房的值,所以若计划要偷第n+1间房子,那么dp[n] 的值为dp[n-2]+nums[n]
若不需要偷取第n+1间房,则dp[n] 的值一定与 dp[n-1] 相等。
从这个结论我们也可以知道,不需要偷取第n间房,则dp[n-1] 的值一定与 dp[n-2] 相等,所以上面不用分情况计算。
由此,我们可以得出转移方程为

dp[n]=max(dp[n1],dp[n2]+nums[n1])dp[n]=max(dp[n-1],dp[n-2]+nums[n-1])

边界条件

这里由题意显然可知,dp[0]dp[0](即只有一间房屋的情况下)的值肯定为nums[0]nums[0],而dp[1]dp[1](有两间房屋的情况)的值则为max(nums[0],nums[1])max(nums[0],nums[1])

代码实现

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        int[] dp=new int[n];
        //边界条件快速判断节省空间
        if(n==1) {
            return nums[0];
        }else {
            //边界条件制定
            dp[0]=nums[0];
            dp[1]=Math.max(nums[0],nums[1]);
            for(int i=2;i<n;i++) {
                dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);//转移方程遍历得出结果
            } 
            return dp[n-1];
        }
    }
}

第二题

213. 打家劫舍 II image.png

思路

最后一步

dp[i] 也是表示在第1间到第i+1间房间中小偷行窃后得到的最高总额

转移方程

dp[n]=max(dp[n1],dp[n2]+nums[n1])dp[n]=max(dp[n-1],dp[n-2]+nums[n-1])

与上一题的区别是这里的房屋是围成一圈的,也就是说第一间屋子跟最后一间屋子不能同时偷,这个时候我们可以知道小偷的行窃计划会分成两个情况:

  1. 偷到第一间房屋的计划
  2. 偷到最后一间房屋的计划

到这里,我们发现好像围成圈也没有太大的变化,我们可以用第一题的代码稍微改一下,把数组长度改为n-1,然后执行两次循环,一次是从第一家偷到倒数第二家,第二个循环是第二家偷到最后一家,然后把两个dp数组的最后一个值作比较,就可以得出题目答案了

边界条件

这里由题意显然可知,dp[0](即只有一间房屋的情况下)的值肯定为nums[0],而dp[1](有两间房屋的情况)的值则为max(nums[0],nums[1])max(nums[0],nums[1]),与上一题做出区别的是,还有一个条件,dp[2](三间房屋情况下),也是只能偷一间屋子,其值为max(nums[0],nums[1],nums[2])max(nums[0],nums[1],nums[2])

代码实现

class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        //边界条件快速判断节省空间
        if(n==1) {
            return nums[0];
        }
        else if(n==2) {
            return Math.max(nums[0],nums[1]);
        }
        else if(n==3){//本来是可以不要这个判断步骤的,但有了这个判断力扣的内存消耗会更少,所以就放上来了
            return Math.max(Math.max(nums[0],nums[1]),nums[2]);
        }
        else {
            int[] dp1=new int[n-1];
            //边界条件制定
            dp1[0]=nums[0];
            dp1[1]=Math.max(nums[0],nums[1]);;
            for(int i=2;i<n-1;i++){
                dp1[i]=Math.max(dp1[i-2]+nums[i],dp1[i-1]);//转移方程遍历得出结果
            }
            int[] dp2=new int[n-1];
            //第二个循环
            dp2[0]=nums[1];
            dp2[1]=Math.max(nums[2],nums[1]);
            for(int i=2;i<n-1;i++){
                dp2[i]=Math.max(dp2[i-2]+nums[i+1],dp2[i-1]);
            }
            return Math.max(dp1[n-2],dp2[n-2]);
        }
    }
}