Day 42 算法记录|动态规划 09 (打家劫舍)

打家劫舍

  • 198.打家劫舍
  • 213.打家劫舍II
  • 337.打家劫舍 III

198.打家劫舍

1.dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
2.dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
3.初始化,dp[0] 和 dp[1],dp[0] 一定是 nums[0],dp[1] = max(nums[0], nums[1]);
3.遍历顺序,dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历!

class Solution {
    public int rob(int[] nums) {
        if(nums ==null||nums.length ==0) return 0;
        if(nums.length ==1) return nums[0];

        int len = nums.length;
        int[] dp = new int[len];
        dp[0]=nums[0];
        dp[1] = Math.max(nums[0],nums[1]);

        for(int i =2;i<nums.length;i++){
            dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
        }

return dp[len-1];
    }
}

进一步对滚动数组的空间优化 dp数组只存与计算相关的两次数据
因为当前能偷的最多与上一家还有上上一家有关

class Solution {
    public int rob(int[] nums) {
   
        if(nums.length ==1) return nums[0];

        int len = nums.length;
        int[] dp = new int[2];
        dp[0]=nums[0]; //上上家
        dp[1] = Math.max(nums[0],nums[1]); // 上一家
        int res =0;

        for(int i =2;i<nums.length;i++){
            res = Math.max(dp[1],dp[0]+nums[i]); //当前家的最大值
            dp[0] = dp[1];
            dp[1] = res;

        }

    return dp[1];
    }
}

213.打家劫舍II

这个视频讲解的不错
这道题与上一道不同的是首尾相连
那么第一个和最后一个一定只能选择其中一个
Day 42 算法记录|动态规划 09 (打家劫舍)_第1张图片

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

    }

    public int robAction(int [] nums){
        if(nums.length ==1) return nums[0];

        int[] dp = new int[2];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0],nums[1]);
        int temp =0;
        for(int i=2;i<nums.length;i++){
            temp = Math.max(dp[1],dp[0]+nums[i]);
            dp[0] = dp[1];
            dp[1] = temp;
        }
        return dp[1];
    }
}

337.打家劫舍 III

每个节点是否被选中,取决于该节点的左右子节点是否被选
方法一递归
从根节点开始遍历,分为两种情况,偷当前节点和孙子节点,不偷当前节点,偷孩子
再对每个结点比较大小

class Solution {
    // 1.递归去偷,超时
    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));
    }
    }

上面的递归超时,因为出现了很多重复的步骤,改进记忆法递归
方法二递归去偷,记录状态
从根节点开始遍历的时候,就用一个类似数组的集合进行存放,减少遍历次数

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int rob(TreeNode root) {
    Map<TreeNode,Integer> memo = new HashMap<>();
    return robAction(root,memo);
    }

    public int robAction(TreeNode root, Map<TreeNode,Integer> memo ){
       //停止条件
       if(root == null) return 0;

       // 避免重复操作
        if(memo.containsKey(root)){
            return memo.get(root);
        }

      
        //1.选择当前节点 再偷孙子
          int cur = root.val;
        if(root.left !=null){
            cur +=robAction(root.left.left,memo)+robAction(root.left.right,memo);
        }

        if(root.right != null){
            cur +=robAction(root.right.left,memo) + robAction(root.right.right,memo);
        }
        //2.不偷当前节点,去偷孩子
        int  cur1 = robAction(root.left,memo)+robAction(root.right,memo);
        //比较上面两种选择
        int res = Math.max(cur,cur1);
        memo.put(root,res);
        return res;
    }
}

方法三: 状态标记递归
每个节点是否选择,取决于其左右子节点(所以只需要记录临近节点的状态,其余就不用管,就像上一道里面的上一个,上上个)

dp[0],dp[1]分别表示当前节点没有被选中,和被选中
left[0],left[1]表示其左孩子没选中和选中,right同样

对于当前节点:
不偷:Max(左孩子不偷,左孩子偷) + Max(又孩子不偷,右孩子偷),我不偷,我的孩子可偷可不偷
root[0] = Math.max(rob(root.left)[0], rob(root.left)[1]) +Math.max(rob(root.right)[0], rob(root.right)[1])
偷:左孩子不偷+ 右孩子不偷 + 当前节点偷,我偷,孩子必然不能偷

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

    public int[] robAction(TreeNode root ){
       //停止条件
       if(root == null) return new int[2];

        
       int[] left = robAction(root.left);
       int[] right = robAction(root.right);
       int[] dp = new int[2]; 

       dp[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);//当前节点没有被选中
       dp[1] = root.val + left[0] + right[0];//被选中
        return dp;
    }
}

你可能感兴趣的:(算法,动态规划,数据结构)