这道题目描述了一个小偷在二叉树结构的房屋群中行窃的场景。每个节点代表一个房屋,节点值代表该房屋的财物价值。小偷需要遵守一个规则:不能同时偷窃直接相连的两个房屋(即父子节点不能同时被偷),否则会触发警报。
从算法角度看,这是一个典型的树形动态规划问题。我们需要在二叉树结构上计算最优解,同时满足特定约束条件。与线性结构的打家劫舍问题不同,树形结构增加了问题的复杂度,因为每个节点的决策会影响其子节点和父节点的选择。
对于树中的每个节点,我们定义两种状态:
这两种状态将作为我们动态规划的基础。通过后序遍历(左右根)的方式,我们可以自底向上地计算每个节点的这两种状态值。
对于任意节点,其状态转移遵循以下规则:
如果偷当前节点,则不能偷其直接子节点:
robNow = node.val + left[1] + right[1]
(其中left[1]和right[1]分别表示不偷左右子节点时的最大收益)
如果不偷当前节点,则可以自由选择是否偷子节点(取最优解):
notRobNow = max(left[0], left[1]) + max(right[0], right[1])
最终,根节点的最大收益就是max(robNow, notRobNow)。
java复制class Solution {
public int rob(TreeNode root) {
int[] res = robTree(root);
return Math.max(res[0], res[1]);
}
private int[] robTree(TreeNode node) {
if (node == null) {
return new int[]{0, 0};
}
int[] left = robTree(node.left);
int[] right = robTree(node.right);
// 偷当前节点,则不能偷子节点
int robNow = node.val + left[1] + right[1];
// 不偷当前节点,则可选择偷或不偷子节点(取最大值)
int notRobNow = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
return new int[]{robNow, notRobNow};
}
}
robTree方法返回一个长度为2的数组:
递归终止条件:当节点为null时,返回[0,0],表示没有收益
后序遍历处理:
最终结果取根节点两种状态的最大值
该算法采用后序遍历,每个节点只被访问一次,因此时间复杂度为O(n),其中n是树中节点的数量。空间复杂度取决于递归栈的深度,最坏情况下(树退化为链表)为O(n)。
虽然上述解法已经比较高效,但对于某些语言(如Python)的递归实现,可能会因为递归深度过大导致栈溢出。可以考虑使用迭代式的后序遍历(使用栈)来避免递归带来的问题。
在实际编码时需要注意:
如果题目要求不仅返回最大金额,还要返回具体的偷窃节点列表,我们可以修改状态数组,使其不仅包含金额,还包含对应的节点集合。不过这会使空间复杂度增加。
类似的树形DP问题还有:
这些问题的解决思路都是定义合适的状态,然后通过后序遍历进行状态转移。
在实际面试或竞赛中遇到这类问题时,建议:
对于树形DP问题,后序遍历通常是首选,因为它自然地提供了自底向上的计算顺序。同时,定义清楚的状态是解决问题的关键,这需要一定的练习和经验积累。
提示:在练习树形DP问题时,可以先用小规模的树手动模拟计算过程,确保完全理解状态转移的逻辑,这有助于写出正确的代码。