LeetCode 337:打家劫舍 III(House Robber III)------ 题解 ✅
📖 内容概要
在上次打家劫舍的基础上,小偷发现了一个新的可行区域:
二叉树结构的房屋 。
如果 直接相连的节点(父节点和子节点)在同一晚被闯入,系统会自动报警。
求在不触动警报的前提下,一夜之间能够偷窃到的最高金额。
✅ 树形 DP
✅ 后序遍历(自底向上)
✅ 打家劫舍系列第三题
💡 解题思路(核心)
一、关键难点
- 房屋不再是线性或环形
- 而是 二叉树结构
- 父子节点不能同时偷
二、状态定义(非常关键)
对于以 root 为根的树,返回两个值:
text
res[0] = 不偷 root 时,子树的最大金额
res[1] = 偷 root 时,子树的最大金额
三、状态转移(树形 DP)
✅ 偷当前节点
java
val2 = root.val + left[0] + right[0];
- 必须 不能偷左右子节点
✅ 不偷当前节点
java
val1 = max(left[0], left[1]) + max(right[0], right[1]);
- 左右子树自由选择最优方案
四、递归 + 后序遍历
先计算左子树
再计算右子树
最后合并当前节点
✅ 自底向上
✅ 无重复计算
✅ AC 代码(Java)
java
/**
* Definition for a binary tree node.
*/
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) {
int[] res = help(root);
return Math.max(res[0], res[1]);
}
// 返回 [不偷当前节点的最大值, 偷当前节点的最大值]
public int[] help(TreeNode root) {
if (root == null) {
return new int[]{0, 0};
}
int[] left = help(root.left);
int[] right = help(root.right);
// 不偷当前节点
int val1 = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
// 偷当前节点
int val2 = root.val + left[0] + right[0];
return new int[]{val1, val2};
}
}
⏱️ 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间复杂度 | O(n)(每个节点只访问一次) |
| 空间复杂度 | O(h)(递归栈,h 为树高) |
🔍 打家劫舍三部曲对比
| 题目 | 结构 | 解法 |
|---|---|---|
| 198 | 线性街道 | 一维 DP |
| 213 | 环形 | 拆环为链 |
| 337 | 二叉树 | 树形 DP |
✅ 一句话总结
树形 DP = 后序遍历 + 每个节点返回"偷 / 不偷"两种状态的最大值。
📌 面试加分点(建议记住)
- ✅ 为什么返回数组而不是单个值?
- ✅ 为什么是后序遍历?
- ✅ 如何把树形 DP 抽象成状态机?
- ✅ 与记忆化搜索的关系