337. 打家劫舍 III
题目来源
题目分析
小偷发现了一个新的可行窃地区,这个地区排列成一棵二叉树。由于相邻的房屋同时被盗会触发报警,小偷需要在不触动报警的情况下,盗取能够盗取的最高金额。
题目难度
- 难度:中等
题目标签
- 标签:动态规划, 树, DFS
题目限制
1 <= nums.length <= 100
0 <= nums[i] <= 400
解题思路
思路:DFS + 动态规划
-
定义问题:
- 每个节点有两个状态:被盗或者不被盗。
- 如果当前节点被盗,那么它的子节点不能被盗。
- 如果当前节点不被盗,那么它的子节点可以选择被盗或者不被盗。
-
状态转移:
- 利用 DFS 递归遍历整棵树,在每个节点处计算两种状态下的最大值。
-
递归公式:
- 对于每个节点
root
,我们计算出两种情况的最大收益:Select
: 当前节点被盗的最大收益。NotSelect
: 当前节点不被盗的最大收益。
- 对于每个节点
-
最终结果:
- 对根节点的两种状态取最大值,即为所求的最高金额。
核心算法步骤
-
DFS 递归:
- 对每个节点进行 DFS 递归,计算左右子树的最大收益。
-
状态记录:
- 使用两个数组记录被盗和不被盗的最大收益。
-
返回最高金额:
- 根节点的两种状态中取最大值,即为最终的最高盗窃金额。
代码实现
以下是解决打家劫舍 III 问题的 Java 代码:
/**
* 337. 打家劫舍 III
* @param root 二叉树
* @return 最高金额
*/
public int rob(TreeNode root) {
int[] robbed = rob2(root);
return Math.max(robbed[0], robbed[1]);
}
public int[] rob2(TreeNode root) {
if (root == null) {
return new int[]{0, 0};
}
int[] left = rob2(root.left);
int[] right = rob2(root.right);
// 如果不选择当前节点,则可以选择左右子节点的最大值
int notSelect = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
// 如果选择当前节点,则左右子节点不能选择
int Select = root.val + left[0] + right[0];
return new int[]{notSelect, Select};
}
代码解读
rob2(TreeNode root)
是一个递归函数,用于计算当前节点root
两种状态下的最大收益。notSelect
记录当前节点不被盗时的最大收益。Select
记录当前节点被盗时的最大收益。- 最终返回一个数组,分别表示不被盗和被盗的两种情况。
性能分析
- 时间复杂度:
O(N)
,其中N
是树的节点数量。每个节点遍历一次。 - 空间复杂度:
O(N)
,由于递归栈的空间开销。
测试用例
以下是一些测试用例,用于验证代码的正确性:
TreeNode<Integer> root1 = new TreeNode<>(3);
root1.left = new TreeNode<>(2);
root1.right = new TreeNode<>(3);
root1.left.right = new TreeNode<>(3);
root1.right.right = new TreeNode<>(1);
System.out.println(rob(root1)); // 输出: 7
TreeNode<Integer> root2 = new TreeNode<>(3);
root2.left = new TreeNode<>(4);
root2.right = new TreeNode<>(5);
root2.left.left = new TreeNode<>(1);
root2.left.right = new TreeNode<>(3);
root2.right.right = new TreeNode<>(1);
System.out.println(rob(root2)); // 输出: 9
效果
总结
通过 DFS 和动态规划结合,我们能够有效地求解打家劫舍 III 问题。此方法思路清晰,效率较高,适用于大多数场景。