题目
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
思路
最值:穷举 动态规划
base case:null 0;
状态:偷盗的金额,也就是不直接相邻的节点和
选择:它不相邻的节点
bp函数:需要实现最大金额的计算;
bp函数分析:来到一个节点,两种情况:1)选择偷它,然后选择偷它的孙子节点 2)不选择偷它然后偷选择它的子节点,比较二者值的大小,返回偷到的金额
可以用备忘录记录偷过的节点
代码
public int rob(TreeNode root) {
HashMap<TreeNode,Integer> map = new HashMap<>();
return bp(root,map);
}
public int bp(TreeNode root,HashMap<TreeNode,Integer> map){
if (root==null){
return 0;
}
if (map.containsKey(root)){
return map.get(root);
}
int l = bp(root.left,map);
int r = bp(root.right,map);
int a = l+r;
int b = root.val;
if (root.left != null){
b += bp(root.left.left,map)+bp(root.left.right,map);
}
if (root.right != null){
b += bp(root.right.left,map)+bp(root.right.right,map);
}
map.put(root,Math.max(a,b));
return Math.max(a,b);
}
}
复制代码
debug
public int rob(TreeNode root) {
// 用map当备忘录记录偷过的节点
HashMap<TreeNode,Integer> map = new HashMap<>();
return bp(root,map);
}
public int bp(TreeNode root,HashMap<TreeNode,Integer> map){
if (root==null){
return 0;
}
if (map.containsKey(root)){
return map.get(root);
}
int l = bp(root.left,map);
int r = bp(root.right,map);
// 不偷这个点
int a = l+r;
// 偷这个节点
int b = root.val;
if (root.left != null){
b += bp(root.left.left,map)+bp(root.left.right,map);
}
if (root.right != null){
b += bp(root.right.left,map)+bp(root.right.right,map);
}
// 放进map里
map.put(root,Math.max(a,b));
// 返回大值
return Math.max(a,b);
}
}
复制代码
如果要调用root.left.right
需要对root.left
进行null 判断
代码优化
每个节点可选择偷或者不偷两种状态,根据题目意思,相连节点不能一起偷
当前节点选择偷时,那么两个孩子节点就不能选择偷了
当前节点选择不偷时,两个孩子节点只需要拿最多的钱出来就行(两个孩子节点偷不偷没关系)
我们使用一个大小为 2 的数组来表示 int[] res = new int[2] 0 代表不偷,1 代表偷
任何一个节点能偷到的最大钱的状态可以定义为
当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
(观察上面发现,左右子树的两个状态影响当前子树的两个状态,但别的子树的状态影响不了。
因此没必要用 map 记录每一个子树的状态,递归总是子调用的解返回给父调用,所以只需在每次递归中用两个变量,存当前子问题的两个状态,返回出来给父调用即可)
第 1 步:状态定义
dp[node][j] :这里 node 表示一个结点,以 node 为根结点的树,并且规定了 node 是否偷取能够获得的最大价值。
j = 0 表示 node 结点不偷取;
j = 1 表示 node 结点偷取。
第 2 步: 推导状态转移方程
根据当前结点偷或者不偷,就决定了需要从哪些子结点里的对应的状态转移过来。
如果当前结点不偷,左右子结点偷或者不偷都行,选最大者;
如果当前结点偷,左右子结点均不能偷。
(状态转移方程的表述有点复杂,请大家直接看代码。)
第 3 步: 初始化
一个结点都没有,空节点,返回 0,对应后序遍历时候的递归终止条件;
第 4 步: 输出
在根结点的时候,返回两个状态的较大者。
第 5 步: 思考优化空间
优化不了。
(作者:liweiwei1419
链接:leetcode-cn.com/problems/ho…
)
public int rob(TreeNode root) {
int[] result = bp(root);
return Math.max(result[0],result[1]);
}
public int[] bp(TreeNode root){
if(root==null){
return new int[2];
// return new int[]{0, 0} 这种也可以,表示放00进去
}
int[] res =new int[2];
int[] left =bp(root.left);
int[] right = bp(root.right);
res[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
res[1] = root.val + left[0] +right[0];
return res;
}
}
复制代码