Leetcode day4:打家劫舍III

原题:

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
(话说这个小偷这么聪明为什么不去当程序员)
示例 1:
在这里插入图片描述
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
在这里插入图片描述
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
这里的TreeNode:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int val) { this.val = val;
 *  	}
 *  }
 * }
 */

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber-iii

思路和代码:

先分享一个错误思路,虽然是错误的,但是对层序遍历还是一个很好的方法,也即计算出奇数和偶数层分别的和再求最大值。
大概的思路就是利用队列进行层序遍历,然后再利用curLevel和nextLevel分别记录当前和下一层的节点数。
当当前的层有节点出队列时,curLevel–,当有下一层节点入队列时,nextLevel++。当curLevel==0时,将nextLevel赋给curLevel,nextLevel置0。
顺带说一句,curLevel初始值为1(因为第一层已经确定有根节点),nextLevel初始值为0。

层序遍历代码(与题意不符):
 public int rob(TreeNode root) {
	        int odd = 0;
	        int even = 0;
	        int loft = 0;
	        int curLevel = 1;
	        int nextLevel = 0;
	        TreeNode now = root;
	        Queue<TreeNode> q = new LinkedList<TreeNode>();
	        q.offer(now);
	        if(now==null)return 0;
	        while(!q.isEmpty()){
	        	now = q.poll();
	        	curLevel--;
	        	
	        	if(loft%2==0) {
	        		even += now.val;
	        	}
	        	else{
	        		odd += now.val;
	        	}
	        	
	        	if(now.left!=null) {
	        		q.offer(now.left);
	        		nextLevel++;
	        	}
	        	if(now.right!=null) {
        			q.offer(now.right);
        			nextLevel++;
        		}
	        	if(curLevel==0) {
	        		curLevel = nextLevel;
	        		nextLevel = 0;
	        		loft++;
	        	}
	        }
	
	        return Math.max(odd,even);
	    }

为什么说与题意不符呢,因为当出现:
在这里插入图片描述
这样的情况时,9可以是:4+5,也可以是:5+3+1(第三层的3和第一个1)所以并不一定是简单的隔层相加。应该用递归和动态规划来做。

简单递归思路:

很简单的代码,就像白话一样,不解释了,直接上代码。

简单递归代码:
class Solution {
   	   public int rob(TreeNode root) {
         if(root == null)return 0;
         if(root.left == null&&root.right == null){
             return root.val;
         }
         int left = 0,right = 0,valleft = 0,valright = 0;
         left = rob(root.left);
         right = rob(root.right);
         if(root.left!=null) {
        	 valleft = rob(root.left.left) + rob(root.left.right);
         }
         else valleft = 0;
         if(root.right!=null) {
        	 valright = rob(root.right.left) + rob(root.right.right);
         }
         else valright = 0;
         return Math.max(root.val+valleft+valright,left+right);
    }
}
动态规划思路:

优化子结构和子问题的重复性就不证了,一目了然。简单来说就是用一个Map把算过的值存储起来,需要的时候直接从map里取就可以了。

动态规划代码:
public class Solution {
	 public int rob(TreeNode root) {
		 Map<TreeNode,Integer> m = new HashMap<>();
		 return new_rob(root,m);
    }
	 int new_rob(TreeNode root,Map<TreeNode,Integer> m) {
		
	        int left = 0,right = 0,valleft = 0,valright = 0,result = 0;
	        if(root == null)return 0;
	        if(root.left == null&&root.right == null) {
	        	return root.val;
	        }
	        if(m.containsKey(root)) {
	        	return m.get(root);
	        }
	       
	     
	        right = new_rob(root.right,m);
	        left = new_rob(root.left,m);
	        
	      if(root.right!=null) {
	    		 valright = new_rob(root.right.left,m) + new_rob(root.right.right,m);
	      	}
	      else valright = 0;
	      if(root.left!=null) {
	    		 valleft = new_rob(root.left.left,m) + new_rob(root.left.right,m);
	      	}
	      else valleft = 0;
	      result = Math.max(root.val+valleft+valright,left+right);
	      m.put(root, result);
	      return result;
	 }

}

其实这个方法还有优化的空间,就是因为这两种解法都用到了孙子节点,即使是在有记忆化的情况下还是会有损耗,所以这就是优化的突破口。

优化思路:

(这一部分是借鉴大佬的)
换一种办法来定义此问题
每个节点可选择偷或者不偷两种状态,根据题目意思,相连节点不能一起偷
当前节点选择偷时,那么两个孩子节点就不能选择偷了
当前节点选择不偷时,两个孩子节点只需要拿最多的钱出来就行(两个孩子节点偷不偷没关系)
我们使用一个大小为 2 的数组来表示 int[] res = new int[2] 0 代表不偷,1 代表偷。
所以计算公式就是:
root[0] = Math.max(rob(root.left)[0], rob(root.left)[1]) + Math.max(rob(root.right)[0], rob(root.right)[1])
root[1] = rob(root.left)[0] + rob(root.right)[0] + root.val;
这样就可以避开计算算子节点了。
附代码:

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

作者:reals
链接:https://leetcode-cn.com/problems/house-robber-iii/solution/san-chong-fang-fa-jie-jue-shu-xing-dong-tai-gui-hu/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值