原题:
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
(话说这个小偷这么聪明为什么不去当程序员)
示例 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/