打家劫舍系列题目总结

1.打家劫舍

思路

常规的动态规划题目

①dp[i]的意思是偷第i间房子的最大金额数

②递推dp[i]=max(dp[i-1],dp[i-2]+nums[i]),其实就是求第i间房子最大金额,就是要么偷这间房,要么不偷,如果偷,那么前一个不能偷,也就是找dp[i-2]的最大金额+当前可以偷的nums[i]。如果不偷,那么最大金额就是从上一间房子得出。因为到第i间没变化,直接参照上一间就可以了(因为都是最大)。

③初始化dp[0]肯定是nums[0],但是第二间房肯定就是从nums[0]和nums[1]中选择,因为第二间房子偷还是不偷都会对第一间房偷不偷有影响。按照上面的递推即可。

④遍历自底向上就可以了

时间复杂度是n,空间复杂度也是n

但是可以优化成点,每次都是2个数值在操作,dp[i-1]和dp[i-2]那么只需要两个临时变量带着就行。

class Solution {
    public int rob(int[] nums) {
        if(nums.length==0) return 0;
        if(nums.length==1) return nums[0];
        
        // int[] dp=new int[nums.length];
        // dp[0]=nums[0];
        // dp[1]=Math.max(nums[0],nums[1]);
        int pre=nums[0];
        int cur=Math.max(nums[0],nums[1]);
        
        for(int i=2;i<nums.length;i++){
            int temp=Math.max(cur,pre+nums[i]);
            pre=cur;
            cur=temp;
            
        }
        return cur;
    }
}

2.打家劫舍2

思路

跟原来的那个思路非常像,问题是怎么处理环形?其实处理环形可以分为两种情况,第一种是偷第1个,第二种是偷最后一个。偷第一个的话最后一个是没有办法偷的,相同偷最后一个那么第一个也是不能偷的。也就是分成了两个数列求解,实际上就是求0–n-1的和1–n的数列打劫劫舍。假设期间的两个点第k个和第k+1个点,因为是环所以无论把谁看做是起点都是差不多的,这个时候递推还是相同,只不过分了两个序列。但是问题来了,假设要偷第k+1(相当于是0)个,那么有没有一种可能就是第k个没有被偷呢?那么结果可能就是dp[k+1]=dp[k]+num。但是问题就是dp[k]如果没有被偷那么就是等于dp[k-1]的最大金额了。也就是dp[k+1]=dp[k]+num=dp[k-1]+num

①dp[i]的意义是前i间房可以偷到的最大钱

②递推同上

③初始化同上

④遍历从左到右

class Solution {
    public int rob(int[] nums) {
        if(nums.length==0) return 0;
        if(nums.length==1) return nums[0];
        
        return Math.max(robIt(Arrays.copyOfRange(nums,0,nums.length-1)),
        robIt(Arrays.copyOfRange(nums,1,nums.length)));
          
    }

    public int robIt(int[] nums){
        if(nums.length==0) return 0;
        if(nums.length==1) return nums[0];
        int pre=nums[0];
        int cur=Math.max(nums[0],nums[1]);
        
        for(int i=2;i<nums.length;i++){
            int temp=Math.max(pre+nums[i],cur);
            pre=cur;
            cur=temp;
        }
        return cur;
    }
}

3.打家劫舍3

dfs思路

如果是在树上面去搜索那么可能性就是偷了爷爷节点,那么就不能偷儿子节点,但是可以偷孙子节点。或者是只偷儿子节点。总结来说就是对于每个节点的选择就是要么就偷自己和4个孙子的,要么就是只偷两个儿子节点的。其实递归到每个节点选择都是相同的,最后就是在这两种选择中找到最大的那个。

(超时)时间复杂度是6^n

class Solution {
    public int rob(TreeNode root) {
        if(root==null) return 0;
        
        int money=root.val;
        if(root.left!=null){
            money+=(rob(root.left.left)+rob(root.left.right));
        }

        if(root.right!=null){
            money+=(rob(root.right.left)+rob(root.right.right));
        }

        return Math.max(money,rob(root.left)+rob(root.right));

    }
}

记忆化搜索思路

其实就是通过记录节点的值,防止下次再使用的时候再次递归导致时间复杂度非常高。比如说爷爷节点肯定计算了孙子节点的值,那么如果不记录下来下次计算儿子节点的时候还是会重复计算孙子节点的值。思路基本上是一样的。

class Solution {
    
    public int rob(TreeNode root) {
       Map<TreeNode,Integer> map=new HashMap<>();
       return robMem(root,map);
    }

    public int robMem(TreeNode root,Map<TreeNode,Integer> map){
         if(root==null) return 0;
        if(map.containsKey(root)) return map.get(root);
        int money=root.val;
        if(root.left!=null){
            money+=(robMem(root.left.left,map)+robMem(root.left.right,map));
        }

        if(root.right!=null){
            money+=(robMem(root.right.left,map)+robMem(root.right.right,map));
        }
        int max=Math.max(money,robMem(root.left,map)+robMem(root.right,map));
        map.put(root,max);
        return map.get(root);
    }
}


动态规划思路

这种思路不能够延续之前的记忆化搜索的问题定义,也就是不能在计算爷爷节点的同时计算孙子节点,消耗很大的性能。那么树形的动态规划怎么做?要想知道根节点偷的最多是不是首先得知道孙子节点或者是儿子节点偷的最多的情况?如果是这样那么很明显就是要求到最下面的节点才能够反推上面根节点偷的最大数目。所以这个地方使用后序遍历。问题又来了每个节点应该做什么?

①不偷本节点的,那么儿子节点偷不偷都是可以的,那么也就是返回儿子节点最大的偷到的钱之和就是本节点最大的偷到的金额。

②偷本节点,那么就是儿子节点不能偷的最大偷到的金额+本节点的金钱

问题是怎么存储?返回值是什么?

第一种情况儿子节点需要两种可能返回值第一种是被偷第二种是没被偷。也就是每个节点返回一个数组包括了被偷的情况和没被偷的情况。那么在计算结果的时候才能够取到左右子节点的最大偷到的钱(偷和不偷的情况都需要考虑)

class Solution {
    public int rob(TreeNode root) {
        int[] res=robIt(root);
        return Math.max(res[0],res[1]);
    }

    public int[] robIt(TreeNode root){
        if(root==null) return new int[2];
        int[] res=new int[2];
        
        int[] left=robIt(root.left);
        int[] right=robIt(root.right);

       
        res[0]=Math.max(left[0],left[1])+Math.max(right[0],right[1]);
        res[1]=left[0]+right[0]+root.val;
        return res;
     }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值