力扣刷题 动态规划:打家劫舍全系列

力扣刷题 动态规划:打家劫舍全系列

这个系列有好几道,一个一个来!

文章目录

    • 力扣刷题 动态规划:打家劫舍全系列
      • 198. 打家劫舍
      • 213. 打家劫舍 II
      • 337. 打家劫舍 III
        • 解法一:暴力递归
        • 解法二:暴力递归+记忆化优化
        • 解法三:消除后效性

198. 打家劫舍

难度:简单
题目描述
力扣刷题 动态规划:打家劫舍全系列_第1张图片
解题思路
这是这个系列里最简单的一道啦,状态转移方程很明显。对于第i个房子,要么偷要么不偷,如果偷那金额就等于前两天的金额+当前房子的金额;如果不偷那就等于前一天的金额。每天从这两种选择中选出金额最大的,写成状态转移方程就是:

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

代码也挺简短的,注意边界条件和数组下标,时间和空间复杂度都是O(N)

public int rob(int[] nums) {
        int n = nums.length;
        if(n == 0)
            return 0;
        int[] dp = new int[n+1];
        dp[1] = nums[0];  //初始化第一天
        //今天偷 dp[i-2]+nums[i]  不偷 dp[i-1]
        for(int i = 2;i < n + 1;i++){
            dp[i] = Math.max(dp[i-2] + nums[i-1],dp[i-1]);
        }
        return dp[n];
    }

力扣刷题 动态规划:打家劫舍全系列_第2张图片
状态压缩 因为当前状态只和前一天和前两天的金额有关,所以可以只用两个变量来分别存储,把空间复杂度从O(N)压缩成O(1)

public int rob(int[] nums) {
        int n = nums.length;
        if(n == 0)
            return 0;
        int pre1 = 0,pre2 = 0,max = 0;  //前一天,前两天
        pre1 = nums[0];  //初始化第一天
        //今天偷 pre2+nums[i]  不偷 pre1
        for(int i = 1;i < n;i++){
            max = Math.max(pre2 + nums[i],pre1);
            pre2 = pre1;
            pre1 = max;
        }
        return pre1;
    }

213. 打家劫舍 II

难度:中等
题目描述
力扣刷题 动态规划:打家劫舍全系列_第3张图片
解题思路
这道题和第一道的区别在于房屋围成一圈,最后一个房屋和第一个是紧挨着对,所以如果偷了第一间屋子,就不能偷最后一间。
所以其实就是把数组分成两个,一个0 ~ n-1,一个1 ~ n,在这两个范围内分别用动态规划求值,然后取两者最大值,可以直接用上一道题的代码

public int rob(int[] nums) {
        int n = nums.length;
        if(0 == n)
            return 0;
        if(1 == n)
            return nums[0];
        return Math.max(massage(Arrays.copyOfRange(nums, 0, n - 1)), massage(Arrays.copyOfRange(nums, 1, n)));
    }
    public int massage(int[] nums) {
        int n = nums.length;
        if(n == 0)
            return 0;
        int pre1 = 0,pre2 = 0,max = 0;  //前一天,前两天
        pre1 = nums[0];  //初始化第一天
        //今天偷 pre2+nums[i]  不偷 pre1
        for(int i = 1;i < n;i++){
            max = Math.max(pre2 + nums[i],pre1);
            pre2 = pre1;
            pre1 = max;
        }
        return pre1;
    }

力扣刷题 动态规划:打家劫舍全系列_第4张图片

337. 打家劫舍 III

难度:中等
题目描述
力扣刷题 动态规划:打家劫舍全系列_第5张图片
解题思路
这年头当小偷真不容易.
写了前面两道题,再看到这个,心想这不是小意思嘛,先层序遍历得到每一层的和,然后再用上面的动态规划求最大值,信心满满一提交,结果这个用例让我傻眼了。一层不一定要一次性全抢完
力扣刷题 动态规划:打家劫舍全系列_第6张图片
力扣刷题 动态规划:打家劫舍全系列_第7张图片
然后还是看了评论,应该用树形dp来做
参考题解:三种方法解决树形动态规划问题-从入门级代码到高效树形动态规划代码实现
这篇写的很清楚啦~先用普通递归,然后记忆化递归,然后消除后效性

解法一:暴力递归

力扣刷题 动态规划:打家劫舍全系列_第8张图片

public int rob(TreeNode root) {
			 if(root == null)
				 return 0;
			 int cur = root.val;
			 int left = rob(root.left);
			 int right = rob(root.right);
			 if(root.left != null) {  //偷左子树的两个孩子
				 cur += rob(root.left.left) + rob(root.left.right);
			 }
			 if(root.right != null) {  //偷右子树的两个孩子
				 cur += rob(root.right.left) + rob(root.right.right);
			 }
			 return Math.max(left+right, cur);
 		    }

思路也很清楚啦,要么偷当前根节点和四个孙子,要么偷根节点的左右孩子,取最大值。这样有很多重复的计算,可以用记忆化递归进行优化
力扣刷题 动态规划:打家劫舍全系列_第9张图片

解法二:暴力递归+记忆化优化

用一个哈希表来存储每个节点已经计算出来的值,如果有记录就直接取值

public int rob(TreeNode root) {
			 if(root == null)
				 return 0;
			 HashMap<TreeNode, Integer> memo = new HashMap<>();
			 return robHelper(root, memo);
			 
 		    }
		 public int robHelper(TreeNode root,HashMap<TreeNode, Integer> memo) {
			 if(root == null)
				 return 0;
			 if(memo.containsKey(root))
				 return memo.get(root);
			 int cur = root.val;
			 int left = robHelper(root.left,memo);
			 int right = robHelper(root.right,memo);
			 if(root.left != null) {  //偷左子树的两个孩子
				 cur += robHelper(root.left.left,memo) + robHelper(root.left.right,memo);
			 }
			 if(root.right != null) {  //偷右子树的两个孩子
				 cur += robHelper(root.right.left,memo) + robHelper(root.right.right,memo);
			 }
			 int re = Math.max(left+right, cur);
			 memo.put(root, re);
			 return re;
		}

力扣刷题 动态规划:打家劫舍全系列_第10张图片
快了很多了!但是还能继续优化

解法三:消除后效性

力扣刷题 动态规划:打家劫舍全系列_第11张图片
力扣刷题 动态规划:打家劫舍全系列_第12张图片
根据这个思路可以写出代码

public int rob(TreeNode root) {
			 int [] re = robHelper(root);
			 return Math.max(re[0], re[1]);		 
 		    }
		 public int[] robHelper(TreeNode root) {
			 if(root == null)
				 return new int[] {0,0};
			 int[] re = new int[2];
			 int rob = 0,norob = 0;
			 //抢了这个节点,就不能抢他的孩子;
			 int[] left = robHelper(root.left);
			 int[] right = robHelper(root.right);
			 rob = root.val + left[1] + right[1];
			 //关键在于如果不抢这个节点,对左右两个节点可以选择抢一个或者两个都抢,取可能情况的最大值
			 norob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
			 re[0] = rob;
			 re[1] = norob;
			 return re;
		 }

力扣刷题 动态规划:打家劫舍全系列_第13张图片

你可能感兴趣的:(力扣刷题笔记)