Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
题目:这个小偷偷的地方可有意思了,这个地方只有一个入口,并且里面的房子呈树形相连,每个房子有且只要一个父房子和其相连,此时要求小偷在此区域偷窃,并且不能同时偷两个相连的房子。求小偷可偷盗的最大金额。
解题思路
本题为典型的树形DP的问题,假设此时的树为三层,小偷不能偷相连房子,那么此时小偷可偷的选择就为:
- 偷爷爷结点和全部孙子结点
- 偷所有儿子结点
上述两个金额较大的即为答案,由此可得代码:
public int rob(TreeNode root) {
if(root == null) return 0;
int money = root.val;
if(root.left!=null){
money += rob(root.left.left) + rob(root.left.right);
}
if(root.right!=null){
money += rob(root.right.left) + rob(root.right.right);
}
return Math.max(money, rob(root.left) + rob(root.right));
}
方法较为暴力,喜提超时。那么问题出在哪里呢?原来爷爷在计算孙子的钱的时候也计算了儿子的钱,这样层层递归就会多计算很多步,由此我们可以用空间换时间,将每次的结果保存下来,这样时间复杂度自然下来了,可得代码如下:
public int rob(TreeNode root) {
HashMap<TreeNode, Integer> map = new HashMap<>();
return rob(root, map);
}
public int rob(TreeNode root, HashMap<TreeNode, Integer> map) {
if(root == null) return 0;
if(map.containsKey(root)) return map.get(root);
int money = root.val;
if(root.left!=null){
money += rob(root.left.left, map) + rob(root.left.right, map);
}
if(root.right!=null){
money += rob(root.right.left, map) + rob(root.right.right, map);
}
int result = Math.max(money, rob(root.left, map) + rob(root.right, map));
map.put(root, result);
return result;
}
上述代码最终耗时2ms,超越的人也不多。
在看题解的时候我发现这样一个问题,有人说上面假设的是三层树,结果只有爷爷和孙子的金额和儿子的相比,那么如果此时树是四层,答案也可能是爷爷和重孙的比较大,上面的思路就没有考虑这一点。
**解答:**实际上递归是自底向上进行的,如果此时树存在四层,那么假设我们当前处于第三层(第四层可偷显然是自身),那么由第三层结点作为根节点就只有两种情况:偷自己还是偷儿子!以最终结果来代替作为新的第三层,如果是偷自己,那结果就还是爷爷加孙子,但如果是偷儿子,那结果就是爷爷加重孙!
进阶解法
实际上对于一个结点来说无非只有两种情况:偷或者不偷,那么我们可得:
- 偷:此时的最大金额 = 自身的钱 + 左儿子不偷能偷到的钱 + 右儿子不偷能偷到的钱
- 不偷:此时的最大金额 = 左儿子能偷盗的钱(可偷可不偷自身) + 右儿子能偷盗的钱(可偷可不偷自身)
此时我们可以使用大小为2的数组来表示偷和不偷,代码如下:
public int rob(TreeNode root) {
int[] result = rab(root);
return Math.max(result[0], result[1]);
}
public int[] rab(TreeNode root){
if(root==null) return new int[2];
int[] result = new int[2];
int[] left = rab(root.left);
int[] right = rab(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;
}
最终耗时0ms,牛逼牛逼牛逼!!!