LeetCode打家劫舍系列(经典动态规划系列问题)

题目列表

在这里插入图片描述

逐一攻破

198、打家劫舍(中等)

在这里插入图片描述

看到题目,我们以常规动态规划的思路去思考:

  • 状态:题目有哪些变量?第i间房子的金钱数,每次能够偷盗的房间不允许相邻。目前小偷的位置
  • 于是我们描述出:dp[i]表示偷盗到第i家房子时的最大金额数
  • 思考递推过程,目前的位置在第i家房子,不允许相邻偷盗,则在我们之前的房子中选择能够偷盗的最大金钱max的房子,dp[i] = max+nums[i-1];i-1为第i家房子的金钱。
  • 返回结果:返回所有dp[i]中的最大值;
  • base case:dp[0]表示没有房子,则dp[0]=0;dp[1]肯定是第一家房子能偷,则dp[1] = nums[0];
    得到如下代码:
class Solution {
    public int rob(int[] nums) {
        if(nums==null || nums.length<=0) return 0;
        int[]dp = new int[nums.length+1];//dp[i]表示目前已经偷盗到第i家房子的最大金额

        //base case :dp[0] = 0,dp[1] = nums[0];//第一间房子的价格
        dp[1] = nums[0];

        for(int i=2;i<dp.length;i++){//偷盗到第i家房子
            int max = 0;
            for(int j=i-2;j>=0;j--){
                //相邻房间不能偷,找到之前的最大价格的dp[j];
                if(max<dp[j]){
                    max = dp[j];
                }
            }
            dp[i] = max + nums[i-1];
        }
        //返回所有的dp[i]中的最大价格
        int max = 0;
        for(int n=dp.length-1;n>=0;n--){
            if(max<dp[n]){
                    max = dp[n];
                }
        }
        return max;

    }
}

优化:

class Solution {
    public int rob(int[] nums) {
        if(nums==null || nums.length<=0) return 0;
        int[]dp = new int[nums.length+1];//dp[i]表示目前已经偷盗到第i家房子的最大金额

        //base case :dp[0] = 0,dp[1] = nums[0];//第一间房子的价格
        dp[1] = nums[0];
        int max_now=nums[0];
        for(int i=2;i<dp.length;i++){//偷盗到第i家房子
            int max = 0;
            for(int j=i-2;j>=0;j--){
                //相邻房间不能偷,找到之前的最大价格的dp[j];
                if(max<dp[j]){
                    max = dp[j];
                }
            }
            dp[i] = max + nums[i-1];
             if(max_now<dp[i]){
                    max_now = dp[i];
                }
        }
        return max_now;
    }
}

100%不应该,这里复杂度为时间复杂度O(n2
在这里插入图片描述
我们的思路是找到能偷的最大金钱,但是复杂度却为O(N2),应该可化简。我们尝试修改一下dp数组的定义:
之前:dp[i]表示偷盗到第i间房子得到的最大价格(默认是偷了第i家),我们思考递推的时候是从前i-2间房子中找到最大的偷盗价格,使得我们多了一层遍历。

现在
换个描述,就是dp[i]表示前i个房子能够偷盗到的最大金额,

  • 则我可以不偷第i家,因为我上一次可能偷的是第i-1家,再偷就报警了。
  • 我可以偷第i家,因为我上次偷的是前i-2之前的某一家。

dp[i] = max(dp[i-2]+nums[i-1],dp[i-1]);

base case:dp[0] = 0;第0家房子没得;dp[1] = nums[0];//第一家房子

class Solution {
    public int rob(int[] nums) {
        if(nums==null || nums.length<=0) return 0;
        int[]dp = new int[nums.length+1];//dp[i]表示目前已经偷盗到第i家房子的最大金额

        //base case :dp[0] = 0,dp[1] = nums[0];//第一间房子的价格
        dp[1] = nums[0];//
        for(int i=2;i<dp.length;i++){//偷盗到第i家房子
          dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i-1]);
        }
        return dp[nums.length];
    }
}

时间复杂度O(n),空间复杂度为O(n),也可以使用滚动数组优化:

class Solution {
    public int rob(int[] nums) {
        if(nums==null || nums.length<=0) return 0;
        int temp_1 = nums[0];
        int temp_0 = 0;
        int max = temp_1;
        for(int i=2;i<nums.length+1;i++){//偷盗到第i家房子
          max = Math.max(temp_0+nums[i-1],temp_1);
          temp_0 = temp_1;
          temp_1 = max;
        }
        return max;
    }
}

在这里插入图片描述

213、打家劫舍II(中等)

在这里插入图片描述
添加了环形房间的限制,但是我们必然只能从头偷盗尾,不可能还能绕回来偷(利益最大化的情况下)。我们可以通过割裂问题的方法来做。然后我们又回到了问题1了。

class Solution {
    //第一间和最后一间不能同时被抢,那我就可以把这些房间割裂成两个问题来看
    
    //第一间到倒数第二间
    //第二间到最后一间

    //分别计算出其最大的价格,然后选取最大的值即可。
    public int rob(int[] nums) {
        if(nums==null || nums.length<=0) return 0;
        if(nums.length==1) return nums[0];//只有一间直接返回
        int[]dp1 = new int[nums.length+1];//dp[i]表示目前已经偷盗到第i家房子的最大金额
        int[]dp2 = new int[nums.length+1];

        //base case :dp[0] = 0,dp[1] = nums[0];//第一间房子的价格

        //割裂问题1,第一间到倒数第二间
        dp1[1] = nums[0];//也就是这里的i和num中的是i-1对应
        for(int i=2;i<dp1.length-1;i++){//偷盗到第i家房子
          dp1[i] = Math.max(dp1[i-1],dp1[i-2]+nums[i-1]);
        }
        //
        dp2[1] = nums[1];//这里的i对应num里面的i
        for(int i=2;i<dp2.length-1;i++){
            dp2[i] = Math.max(dp2[i-1],dp2[i-2]+nums[i]);
        } 
        return Math.max(dp2[nums.length-1],dp1[nums.length-1]);

        //割裂问题2:第二间到最后一间
        
    }
}

在这里插入图片描述

337、打家劫舍III(中等)

在这里插入图片描述
这里变到了树的结构,而既然出现了树,我们就要按照做树系列的题目思维来思考:

  • 根结点要做什么?
  • 每个结点要做什么?
  • 左孩子和右孩子哪个先遍历

约束:直接相连的不允许访问,所以我们要根据选择去做。感觉这道题比之前的比较来说,容易想一点:


class Solution {
    Map<TreeNode,Integer> memo = new HashMap<>();
    public int rob(TreeNode root) {
        if(root==null) return 0;
        if(memo.containsKey(root)) return memo.get(root);
        int temp = root.val;//去除当前的值
        int left1 = 0,left=0;//left1表示不含left结点的最大价值
        int right1=0,right=0;
//抢了root,则不能抢其left、right,而是root.left.left/right和root.right.left/right
        left = root.left==null?0:rob(root.left.left)+rob(root.left.right);
        right = root.right==null?0:rob(root.right.left)+rob(root.right.right);
           
        //偷了root,则直接相连的不能访问
        int robNow1 = temp+left+right;
        //没偷root,则可以访问
        int robNow2 = rob(root.left)+rob(root.right);
        int res =  Math.max(robNow2,robNow1);//偷,则返回res,不偷则返回left+right;
        
        memo.put(root,res);
        return res;

    }
}

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨夜※繁华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值