leetcode:337. 打家劫舍 III

题目

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

示例 1:
[中等] 337. 打家劫舍 III - 图1

  1. 输入: root = [3,2,3,null,3,null,1]
  2. 输出: 7
  3. 解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7

示例 2:
[中等] 337. 打家劫舍 III - 图2

  1. 输入: root = [3,4,5,1,3,null,1]
  2. 输出: 9
  3. 解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

示例 3:

  1. 2
  2. / \
  3. 1 3
  4. \
  5. 4
  6. 输入: root = [2,1,3,null,4]
  7. 输出: 7
  8. 解释: 小偷一晚能够盗取的最高金额 3 + 4 = 7

解答 & 代码

二叉树后序遍历 + 动态规划树形 dp

  1. /**
  2. * Definition for a binary tree node.
  3. * struct TreeNode {
  4. * int val;
  5. * TreeNode *left;
  6. * TreeNode *right;
  7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
  8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  10. * };
  11. */
  12. class Solution {
  13. private:
  14. // 递归后序遍历二叉树
  15. // 返回值:长为 2 的数组 {not_rob_result, rob_result}
  16. // - not_rob_result:不偷 root 节点的最大金钱
  17. // - rob_result:偷 root 节点的最大金钱
  18. // 以上指的都是偷以 root 为根节点的二叉树的最大金钱,区别再与偷不偷 root 这个节点
  19. vector<int> robTree(TreeNode* root)
  20. {
  21. // 递归结束条件 / dp 初始化:若当前节点为 NULL,那么偷不偷都是 0(没有可偷的)
  22. if(root == NULL)
  23. return vector<int>{0, 0};
  24. // 递归遍历左、右子树
  25. // 递归得到左子树这棵树,偷左节点和不偷左节点的最大金钱
  26. vector<int> left_result = robTree(root->left);
  27. // 递归得到右子树这棵树,偷右节点和不偷右节点的最大金钱
  28. vector<int> right_result = robTree(root->right);
  29. // 后序处理得到当前节点的结果(当前这棵树可偷的最大金钱)
  30. // 偷 root 节点的最大金钱:偷当前节点,左右子节点就不能偷
  31. int rob_result = root->val + left_result[0] + right_result[0];
  32. // 不偷 root 节点的最大金钱:不偷当前节点,左右子节点就可以偷,
  33. // 但是注意:左、右子节点偷不偷还要看偷、不偷哪个收益大,取最大值
  34. int not_rob_result = max(left_result[0], left_result[1]) + max(right_result[0], right_result[1]);
  35. return vector<int>{not_rob_result, rob_result};
  36. }
  37. public:
  38. int rob(TreeNode* root) {
  39. vector<int> result = robTree(root);
  40. return max(result[0], result[1]);
  41. }
  42. };

复杂度分析:设二叉树节点总数 N

  • 时间复杂度 O(N):后序遍历,每个节点只遍历了一次
  • 空间复杂度 O(logN):递归栈空间复杂度

执行结果:

  1. 执行结果:通过
  2. 执行用时:24 ms, 在所有 C++ 提交中击败了 40.33% 的用户
  3. 内存消耗:31.1 MB, 在所有 C++ 提交中击败了 15.07% 的用户

换种写法:返回值改为 pair 的形式:

  1. /**
  2. * Definition for a binary tree node.
  3. * struct TreeNode {
  4. * int val;
  5. * TreeNode *left;
  6. * TreeNode *right;
  7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
  8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  10. * };
  11. */
  12. class Solution {
  13. private:
  14. // 后序遍历 + 动态规划(树形 dp)
  15. pair<int, int> postOrder(TreeNode* root)
  16. {
  17. if(root == nullptr)
  18. return make_pair(0, 0);
  19. pair<int, int> dpLeft = postOrder(root->left);
  20. pair<int, int> dpRight = postOrder(root->right);
  21. int notRobRoot = max(dpLeft.first, dpLeft.second) + max(dpRight.first, dpRight.second);
  22. int robRoot = root->val + dpLeft.first + dpRight.first;
  23. // cout << "root = " << root->val << ", notRobRoot = " << notRobRoot << ", robRoot = " << robRoot << endl;
  24. return make_pair(notRobRoot, robRoot);
  25. }
  26. public:
  27. int rob(TreeNode* root) {
  28. pair<int, int> dpRoot = postOrder(root);
  29. return max(dpRoot.first, dpRoot.second);
  30. }
  31. };

复杂度分析:设二叉树节点总数 N

  • 时间复杂度 O(N):后序遍历,每个节点只遍历了一次
  • 空间复杂度 O(logN):递归栈空间复杂度

执行结果:

  1. 执行结果:通过
  2. 执行用时:16 ms, 在所有 C++ 提交中击败了 75.94% 的用户
  3. 内存消耗:21.5 MB, 在所有 C++ 提交中击败了 99.81% 的用户