LeetCode打家劫舍House-Robber-动态规划问题

1.打家劫舍 I

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

输入:[ 2, 1, 1, 2]
输出:4
解释:偷第一家和第四家金额最高

  • 1.定义dp数组
    dp[i]表示小偷光顾到第i家时可以拿到的最佳金额,注意,他可以选择偷或者不偷第i家,这取决于后面的状态方程
  • 2.状态转移
    偷第i家,则不能偷第i-1家,dp[i] = dp[i-2] + num[i];
    不偷第i家,因为偷完第i-1家金额诱惑更大:dp[i] = dp[i-1];
    因此,在只有这两情况下,有方程:
    dp[i] = max{ dp[i-2]+nums[i],dp[i-1] }
	public int rob(int[] nums) {
     
        int len = nums.length;
        if(len==0){
     
            return 0;
        }
        if(len==1){
     
            return nums[0];
        }
        if(len==2){
     
            return nums[0]>nums[1]?nums[0]:nums[1];
        }
        int[] dp = new int[len];
        dp[0] = nums[0];
        dp[1] = nums[0]>nums[1]?nums[0]:nums[1];
        for(int i=2;i<len;i++){
     
            dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[len-1];
    }

2. 打家劫舍 II

这里情况发生了改变,所有房屋围成一圈(首尾相连),如何不触发警报拿下最高金额?
由于第一家和最后一家连上了,所有我们要考虑偷第一家还是偷最后一家金额高。
在上题的基础上:

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

3. 打家劫舍 III

房屋由数组排列变成了二叉树排列,规则不变,如何拿最高赏金?

LeetCode打家劫舍House-Robber-动态规划问题_第1张图片
这里仍然涉及到动态规划,只不过要在二叉树里完成
根据不偷相邻房屋原则,我们给小偷哥制定了两种方案:(这里我们形象化二叉树)

  1. 偷1个爷爷节点+4个孙子节点

  2. 偷两个中间父节点的

递归遍历:

	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));
    }

LeetCode打家劫舍House-Robber-动态规划问题_第2张图片
耗时严重,原因是进行了重复计算:爷爷在计算自己能偷多少钱的时候,同时计算了 4 个孙子能偷多少钱,也计算了 2 个儿子能偷多少钱。这样在儿子当爷爷时,就会产生重复计算一遍孙子节点。

记忆化优化

递归到叶子节点时,每计算出一次“最大值”就将root-max以键值对存入HashMap,避免后面的重复计算。

	public int rob(TreeNode root){
     
        HashMap<TreeNode,Integer> map = new HashMap<>();
        return robRecursion(root,map);
    }

    public int robRecursion(TreeNode root,HashMap<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 += ( robRecursion(root.left.left,map)+robRecursion(root.left.right,map) );
        }
        if(root.right!=null){
     
            money += ( robRecursion(root.right.left,map)+robRecursion(root.right.right,map) );
        }
        int max = Math.max(money,robRecursion(root.left,map) + robRecursion(root.right,map));
        map.put(root,max);
        return max;
    }

在这里插入图片描述
效率提升了两个数量级

最后再提供一种思路:

当前节点可以选择偷/不偷,

  1. 当前节点偷,则两个子节点不能偷
  2. 当前节点不偷,则让两个子节点拿出最多的前(假设子节点已拿到最优值)

因此,任意一个节点能够偷到的最大钱可以定义为:

  • 当前不偷:money = 左孩子能偷到的钱 + 右孩子能偷到的钱
  • 当前偷:money = 当前钱 + 左孩子不偷的钱 + 右孩子不偷的钱

用 root[] 数组表示:0为不偷,1为偷

public int rob(TreeNode root) {
     
    int[] result = robInternal(root);
    return Math.max(result[0], result[1]);
}

public int[] robInternal(TreeNode root) {
     
    if (root == null) return new int[2];
    int[] result = new int[2];

    int[] left = robInternal(root.left);
    int[] right = robInternal(root.right);

    result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
    result[1] = left[0] + right[0] + root.val;

    return result;
}

在这里插入图片描述

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