337. 打家劫舍 III
难度: 中等
来源: 每日一题 2023.09.18
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root
。
除了 root
之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root
。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
示例 1:
输入: root = [3,2,3,null,3,null,1]
输出: 7
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7
示例 2:
输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9
提示:
树的节点数在 [1, 10^4] 范围内
0 <= Node.val <= 10^4
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int rob(TreeNode root) {
}
}
分析与题解
-
动态规划 + 层序遍历 结果: 思路失败, GG
这个题目我一开始考虑到的是利用二叉树的层序遍历, 把每一次层的金钱数遍历出来, 但是发现其实上思路是错误的. 因为我们要遵循的条件只有 父子节点不能同时被偷!
为啥这么说呢, 我们看下面的示例图就知道了.
如果按照层序遍历的解题思路, 我们要想获得最大钱数, 我们只能选择第一层和第三层, 如下图所示. 那么最大收益是
1000 + 10 + 10 + 10 + 10 = 1040
但是正确的选择是什么呢? 那就是如下图所示, 最终的收益是
10 + 1000 + 100 = 1110
最后还是要把当时的想法解题思路贴出来, 如下所示.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public int rob(TreeNode root) { if (root == null) { return 0; } // 二叉树的层序遍历, 构建出每层的金额 Deque<TreeNode> deque = new ArrayDeque<>(); deque.add(root); List<Integer> levelMoney = new ArrayList<>(); while(!deque.isEmpty()) { int length = deque.size(); int level = 0; for(int i = 0; i < length; i++) { TreeNode node = deque.poll(); level += node.val; if (node.left != null) {deque.add(node.left);} if (node.right != null) {deque.add(node.right);} } levelMoney.add(level); } int maxRob = levelMoney.get(0); int maxNotRob = 0; int temp = 0; for (int i = 1; i < levelMoney.size(); i++) { temp = maxRob; maxRob = Math.max(maxNotRob + levelMoney.get(i), maxRob); maxNotRob = temp; } return Math.max(maxNotRob, maxRob); } }
结果如下所示.
结果: 思路失败, GG
-
动态规划 + 深度优先遍历 结果: 成功
在先前一次考虑了很久, 也是没有啥思路, 所以就看官方题解了, 我们知道我们唯一需要遵循的条件就是 父子节点不能同时被偷!
那么, 我们需要还是动态规划的思想, 一个节点只能有偷与不偷的两种选择, 要求某一个节点的获取金币数量最大化, 我们可以分情况讨论.
-
当前节点被偷, 那么最大金币数就是
当前节点金币数
+左节点不偷时最大金币数
+右节点不偷时最大金币数
-
当前节点不被偷, 那么最大金币数就是
左节点不偷时与偷时两者中最大金币数
+右节点不偷时与偷时两者中最大金币数
有了以前的状态转移方程思路, 我们首先构建两个HashMap, 用来存储节点选中与不选中时能获取最大金币数.
Map<TreeNode, Integer> f = new HashMap<TreeNode, Integer>(); Map<TreeNode, Integer> g = new HashMap<TreeNode, Integer>();
然后进行深度优先遍历, 遍历的过程, 我们就来根据上面我们总结的动态规划思路来构建我们的HashMap.
public void dfs(TreeNode node) { if (node == null) { return; } dfs(node.left); dfs(node.right); Integer fMoney = node.val + g.getOrDefault(node.left, 0) + g.getOrDefault(node.right, 0); f.put(node, fMoney); Integer gMoney = Math.max(f.getOrDefault(node.left, 0), g.getOrDefault(node.left, 0)) + Math.max(f.getOrDefault(node.right, 0), g.getOrDefault(node.right, 0)); g.put(node, gMoney); }
当构建完成之后, 我们就取出节点中偷与不偷情况比较出最大值就是我们想要的结果. 这样整体的解题过程就完成了.
return Math.max(f.getOrDefault(root, 0), g.getOrDefault(root, 0));
整体的解题思路如下所示.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { Map<TreeNode, Integer> f = new HashMap<TreeNode, Integer>(); Map<TreeNode, Integer> g = new HashMap<TreeNode, Integer>(); public int rob(TreeNode root) { dfs(root); return Math.max(f.getOrDefault(root, 0), g.getOrDefault(root, 0)); } public void dfs(TreeNode node) { if (node == null) { return; } dfs(node.left); dfs(node.right); Integer fMoney = node.val + g.getOrDefault(node.left, 0) + g.getOrDefault(node.right, 0); f.put(node, fMoney); Integer gMoney = Math.max(f.getOrDefault(node.left, 0), g.getOrDefault(node.left, 0)) + Math.max(f.getOrDefault(node.right, 0), g.getOrDefault(node.right, 0)); g.put(node, gMoney); } }
复杂度分析:
- 时间复杂度: O(n), 深度优先遍历需要遍历到每一个节点, 所以递归遍历的次数与节点数量相同.
- 空间复杂度: O(2n), 开辟需要与节点数量2倍的内存空间.
结果如下所示.
-
Comments | 0 条评论