打家劫舍 | 小偷

4 篇文章 0 订阅
4 篇文章 0 订阅

198. 打家劫舍

在这里插入图片描述
看到这题的第一想法是dfs。

1、可以先考虑边界问题:房屋个数有限,不能越界。
if(越界) return 0;

2、不能偷相邻两户

2.1、偷了上一户

2.1.1、这一户不能偷

2.2、没偷上一户

2.2.1、这一户可以偷
2.2.2、这一户可以不偷
2.2.3、两种可能取最优

if(偷了上一户){ return dfs(这一户不能偷);}
else{ return Math.max(dfs(这一户不能偷),dfs(这一户可以偷); }

class Solution {
    public int rob(int[] nums) {
        return dfs(nums,0,false);
    }
    /**
        now_i 当前元素
        flag  上一家有没有偷
     */
    public int dfs(int[] nums, int now_i, boolean flag){
        if(now_i > nums.length-1) return 0;
        if(flag) return dfs(nums,now_i+1,false); 
        // 上一家没偷  这一家可以偷也可以不偷
        else return Math.max(
                dfs(nums,now_i+1,false),
                dfs(nums,now_i+1,true)+nums[now_i]
            ); 
    }
}

很遗憾超时了
通过分析可知道:
在这里插入图片描述
可以看到有相当一部分重复计算了,如果做到只计算一次,其他相同的直接复用即可无需再次计算,那便节约了许多时间。
可以用数组来存储:

    // 记录 这偷家之后 的最优情况
    int m_true[];
    // 记录 这不偷家之后 的最优情况
    int m_false[];
   	..............
   	// -1 表示从未计算过,非负数表示计算得出的值
   	Arrays.fill(m_true,-1);
    Arrays.fill(m_false,-1);
    .............
    // 计算并存储
    if(m_false[i] == -1) m_false[i] = dfs(不偷);
    if(m_true[i] == -1) m_true[i] = dfs();           
    

代码如下:

// 记忆优化

class Solution {
    // 记录 这偷家之后 的最优情况
    int m_true[];
    // 记录 这不偷家之后 的最优情况
    int m_false[];
    public int rob(int[] nums) {
        m_true = new int[nums.length];
        m_false = new int[nums.length];
        Arrays.fill(m_true,-1);
        Arrays.fill(m_false,-1);
        return dfs(nums,0,false);
    }
    /**
        now_i 当前元素
        flag  上一家有没有偷
     */
    public int dfs(int[] nums, int now_i, boolean flag){
        if(now_i > nums.length-1) return 0;
        if(flag) {
            if(m_false[now_i] == -1) m_false[now_i] = dfs(nums,now_i+1,false);
            return m_false[now_i];
        }
        else {
            // 上一家没偷  这一家可以偷也可以不偷
            if(m_false[now_i] == -1) m_false[now_i] = dfs(nums,now_i+1,false);
            if(m_true[now_i] == -1) m_true[now_i] = dfs(nums,now_i+1,true)+nums[now_i];
            return Math.max(m_false[now_i],m_true[now_i]);
        }
         
    }
}

由上面可以启发:用数组效率更高,可以尝试用数组来解。
由于不能偷连续两家,所以可以对比偷不偷当前这家两种结果取最优。
即:Math.max(res[i-2]+nums[i],res[i-1]); res[]表示已偷金额,i表示第几家。

class Solution {
    public int rob(int[] nums) {
        // 当前解法中,数组后移:0~nums.length -> 1~nums.length
        // 所以 Math.max(res[i-2]+nums[i],res[i-1]); -> Math.max(res[i-1]+nums[i],res[i]);
        int[] res = new int[nums.length+1];
        // 不抢第一家
        res[0] = 0;
        // 抢第一家
        res[1] = nums[0];

        for(int i=1; i<nums.length; i++){
            // res[i+1] 就是到第i家(包括第i家是否抢 之后已抢的最大总金额)
            res[i+1] = Math.max(res[i-1]+nums[i],res[i]);
        }
        return res[nums.length];
    }
}

由于 res[i+1] = Math.max(res[i-1]+nums[i],res[i]); 与 return res[nums.length];
所以旧数据仅仅用来推导出新数据,用完即丢。所以可以用3个变量代替数组

class Solution {
    public int rob(int[] nums) {
        // i=1 意味着 必须手动判断 nums.length 的情况
        if(nums.length == 1) return nums[0];

        // 不抢第一家
        int first = 0;        
        // 抢第一家
        int second = nums[0];
        // 最优金额
        int res=0;
        
        for(int i=1; i<nums.length; i++){
            res = Math.max(first+nums[i],second);
            first = second;
            second = res;
        }
        return res;
    }
}

213. 打家劫舍 II

在这里插入图片描述

在第一题的基础上,第一家和最后一家是隔壁

三种情况:
1、偷第一 不偷最后
2、不偷第一 偷最后
3、不偷第一 不偷最后
可以用第一题的解法,三种结果取最大。

class Solution {
    public int rob(int[] nums) {
        // 没得选,只能是它
        if(nums.length == 1) return nums[0];
        // 只能2选1
        if(nums.length == 2) return Math.max(nums[0],nums[1]);
        // 只能3选1
        if(nums.length == 3) return Math.max(Math.max(nums[0],nums[1]),nums[2]);

        /**
            三种情况:
            1、偷第一    不偷最后
            2、不偷第一  偷最后
            3、不偷第一  不偷最后            
         */
         // 1、偷第一    不偷最后
        int[] res = new int[nums.length];
        res[0] = nums[0];
        res[1] = nums[0];
        for(int i=2;i<nums.length-1;i++){
            res[i] = Math.max(res[i-2]+nums[i],res[i-1]);
        }
        int max= res[nums.length-2];


        // 2、不偷第一  偷最后
        res = new int[nums.length];
        res[nums.length-1] = nums[nums.length-1];
        res[nums.length-2] = nums[nums.length-1];
        for(int i=nums.length-3;i>0;i--){
            res[i] = Math.max(res[i+2]+nums[i],res[i+1]);
        }
        max= Math.max(max,res[1]);



        // 3、不偷第一  不偷最后            
        res = new int[nums.length];
        res[0] = 0;
        res[1] = nums[1];
        for(int i=2;i<nums.length-1;i++){
            res[i] = Math.max(res[i-2]+nums[i],res[i-1]);
        }
        max= Math.max(max,res[nums.length-2]);

        
        return max;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值