leetcode:337. 打家劫舍 III
题目
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 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
示例 3:
2
/ \
1 3
\
4
输入: root = [2,1,3,null,4]
输出: 7
解释: 小偷一晚能够盗取的最高金额 3 + 4 = 7
解答 & 代码
二叉树后序遍历 + 动态规划(树形 dp)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
// 递归后序遍历二叉树
// 返回值:长为 2 的数组 {not_rob_result, rob_result}
// - not_rob_result:不偷 root 节点的最大金钱
// - rob_result:偷 root 节点的最大金钱
// 以上指的都是偷以 root 为根节点的二叉树的最大金钱,区别再与偷不偷 root 这个节点
vector<int> robTree(TreeNode* root)
{
// 递归结束条件 / dp 初始化:若当前节点为 NULL,那么偷不偷都是 0(没有可偷的)
if(root == NULL)
return vector<int>{0, 0};
// 递归遍历左、右子树
// 递归得到左子树这棵树,偷左节点和不偷左节点的最大金钱
vector<int> left_result = robTree(root->left);
// 递归得到右子树这棵树,偷右节点和不偷右节点的最大金钱
vector<int> right_result = robTree(root->right);
// 后序处理得到当前节点的结果(当前这棵树可偷的最大金钱)
// 偷 root 节点的最大金钱:偷当前节点,左右子节点就不能偷
int rob_result = root->val + left_result[0] + right_result[0];
// 不偷 root 节点的最大金钱:不偷当前节点,左右子节点就可以偷,
// 但是注意:左、右子节点偷不偷还要看偷、不偷哪个收益大,取最大值
int not_rob_result = max(left_result[0], left_result[1]) + max(right_result[0], right_result[1]);
return vector<int>{not_rob_result, rob_result};
}
public:
int rob(TreeNode* root) {
vector<int> result = robTree(root);
return max(result[0], result[1]);
}
};
复杂度分析:设二叉树节点总数 N
- 时间复杂度 O(N):后序遍历,每个节点只遍历了一次
- 空间复杂度 O(logN):递归栈空间复杂度
执行结果:
执行结果:通过
执行用时:24 ms, 在所有 C++ 提交中击败了 40.33% 的用户
内存消耗:31.1 MB, 在所有 C++ 提交中击败了 15.07% 的用户
换种写法:返回值改为 pair 的形式:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
// 后序遍历 + 动态规划(树形 dp)
pair<int, int> postOrder(TreeNode* root)
{
if(root == nullptr)
return make_pair(0, 0);
pair<int, int> dpLeft = postOrder(root->left);
pair<int, int> dpRight = postOrder(root->right);
int notRobRoot = max(dpLeft.first, dpLeft.second) + max(dpRight.first, dpRight.second);
int robRoot = root->val + dpLeft.first + dpRight.first;
// cout << "root = " << root->val << ", notRobRoot = " << notRobRoot << ", robRoot = " << robRoot << endl;
return make_pair(notRobRoot, robRoot);
}
public:
int rob(TreeNode* root) {
pair<int, int> dpRoot = postOrder(root);
return max(dpRoot.first, dpRoot.second);
}
};
复杂度分析:设二叉树节点总数 N
- 时间复杂度 O(N):后序遍历,每个节点只遍历了一次
- 空间复杂度 O(logN):递归栈空间复杂度
执行结果:
执行结果:通过
执行用时:16 ms, 在所有 C++ 提交中击败了 75.94% 的用户
内存消耗:21.5 MB, 在所有 C++ 提交中击败了 99.81% 的用户