一、题目
二、思路
一眼dp,关键是怎么去构建dp方程。
在打家劫舍I中,我们的数据结构是数组类型的,只需要从前到后遍历数组维护最大值即可 。当时我们是从头开始考虑,n=1,那么最大值就是这一家;n=2,那么最大值就是两家中最大的一家;n=3,那就分为上一家没被偷或者被偷了的情况,如果被偷了,那么这家不可以偷;如果没被偷,那么这家就是被偷的。即dp[i]=max(dp[i-1],dp[i-2]+nums[i])。
在打家劫舍III中,数据结构变为二叉树,其实还是分两种情况,被偷或者没被偷,因为我们要求整棵树的最大总和,那么就可以设f(o)为o结点被选中时,当前节点及其左右子树的最大值,g(o)为o结点没被选中时,当前节点及其左右子树的最大值。o被选中时,o的左右孩子不能被选中,所以f(o)=o.val+g(o.l)+g(o.r);o没被选中时,o的左右孩子可以被选中,也可以不被选中,我们只需要各自判断一边没被选中或者被选中的情况下谁大就可以了,所以g(o)=max(f(o.l),g(o.l))+max(f(o.r),g(o.r))。根据动态转移方程,求o的话要知道o的孩子,那么我们可以dfs后续遍历二叉树,自底向上进行遍历。同时用哈希表f、g存储结点信息,key为结点,val为最大值。
三、代码
/**
* dp:每个结点只有两种状态,即被选中或者没被选中,那么设f(o)代表o结点被选中时,当前节点及其子树的最大值;
* g(o)代表结点o没被选中时,当前节点及其子树的最大值。构建动态转移方程为:
* o被选中时,左右孩子不可被选中:
* f(o)=g(o.left)+g(o.right)+o.val
* o未被选中时,左右孩子可以被选中也可以不被选中,分别找最大值即可:
* g(o)=max(f(o.left),g(o.left))+max(f(o.right),g(o.right))
* 对于树的问题,可以用哈希表存储f和g的函数值
*/
class Solution {
private Map<TreeNode, Integer> f = new HashMap<>(); //被选中
private Map<TreeNode, Integer> g = new HashMap<>(); //没被选中
public int rob(TreeNode root) {
dfs(root);
return Math.max(f.get(root), g.get(root));
}
//利用dfs后续遍历树,自底向上构建哈希表
public void dfs(TreeNode root) {
if (root == null) { //递归终止条件
return;
}
//后序遍历
dfs(root.left);
dfs(root.right);
f.put(root, root.val + g.getOrDefault(root.left, 0) + g.getOrDefault(root.right, 0));
g.put(root, Math.max(f.getOrDefault(root.left, 0), g.getOrDefault(root.left, 0)) + Math.max(f.getOrDefault(root.right, 0), g.getOrDefault(root.right, 0)));
}
}
注意:
从底部构建dp时,结点的孩子可能为null,需要使用到getOrDefault方法。