题目链接:https://leetcode-cn.com/problems/house-robber-iii/
题目描述
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
思路
该题的思路本质上是一个动态规划的过程,从根节点往下可以不断拆分成最优子问题,递归地解决;在回溯过程中汇总左右子树以及根节点的结果。在整个过程中不断更新最大收益。
该题只需要遍历一遍树即可,不需要额外空间。
核心的递归函数为robCore(TreeNode* root, int& prevSum)
prevSum
是一个引用,用来返回以root
为根节点的树中,不包含根节点的最大收益;
函数返回值是包含根节点的最大收益;
动态方程:
某个树的最大收益 = max(包含根节点的最大收益,以及不包含根节点的最大收益);
不包含根节点的最大收益 = 左子树的最大收益 + 右子树最大收益
包含根节点的最大收益 = 不包含左子节点的左子树最大收益 + 根节点 + 不包含右子节点的最大收益
maxSum = max(maxSum,当前树的最大收益)
复杂度分析
- 时间复杂度:O(n)。只遍历一遍所有节点
- 空间复杂度:O(n)。递归栈的调用,如果树极度不平衡,空间复杂度为O(n);如果树平衡,为O(log N)。
代码
class Solution {
int maxSum = 0;
public:
int rob(TreeNode* root) {
if(!root) return 0;
int prevSum = 0;
robCore(root, prevSum);
return maxSum;
}
private:
// 返回值:包含该节点为的最大收益;
// 引用返回prevSum:不包含末尾节点的最大收益
int robCore(TreeNode* root, int& prevSum){
if(!root){
prevSum = 0;
return 0;
}
if(!root->left && !root->right) { // 无左右子树
prevSum = 0;
maxSum = max(maxSum, root->val);
return root->val;
}
int prevLeft = 0, prevRight = 0; // 左右子树不包含左右子节点部分的最大收益
int left = robCore(root->left, prevLeft); // 左子树包含左子节点部分的最大收益
int right = robCore(root->right, prevRight);
int sumWithRoot = root->val+ prevLeft+prevRight; // 包含根节点值的最大收益
prevSum = max(left,prevLeft) + max(right,prevRight); // 不包含根节点的最大收益
maxSum = max(maxSum, max(sumWithRoot,prevSum)); // 更新最大收益
return sumWithRoot;
}
};