LeetCode 337 [House Robber III]

题目链接

题目描述

  • 小偷进入一片区域准备再一次行窃,这片区域仅有一个入口,称为root。在root入口旁边,每个房屋有且仅有一个“父房屋”。聪明的小偷发现这片区域内所有的房屋形成了一棵二叉树。另外,如果同一个晚上,两间直接相连的房屋被闯入的话,自动报警器就会启动。现已知每个房屋里面的金额,要求计算出小偷在不惊动警察的情况下,今晚最多可以偷到多少钱。

解题思路

  1. 抽象一下题目要求,其实本题要计算的是二叉树某些结点的和的最大值,这些结点需要满足的条件是两两不直接相连。
  2. 简单分析可以得出,对于任意一个二叉树结点,可以分两种情况考虑:
    1. 它是被选中的结点,那么它的左右子结点都不是被选中的结点。
    2. 它不是被选中的结点,这种场景可再细分:
      1. 左子结点被选中,右子结点也被选中。
      2. 左子结点被选中,右子结点不被选中。
      3. 左子结点不被选中,右子结点被选中。
      4. 左子结点不被选中,右子结点也不被选中。
    3. 左右子结点的情况,可以递归处理。
  3. 从根结点出发,对于每个结点根据上述分析进行遍历,即可得出结果。理论上,每个结点有两种状态(被选中与不被选中),若结点数为n,那么时间复杂度为O(2^n),但是对于父结点被选中的情况,子结点很多场景其实不需要考虑(称为剪枝),最终时间复杂度远不及O(2^n)。另外需要考虑结点值为负的情况,每个结点在遍历时,应该取0作为初始值。

C++代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int rob(TreeNode* root) {
        int max_sum = 0;
        // 根结点为空,直接返回0
        if (!root) {
            return max_sum;
        }
        
        // 根结点被选中的情况
        int tmp = travel(root, true) + root->val;
        if (tmp > max_sum) {
            max_sum = tmp;
        }
        
        // 根结点未被选中的情况
        tmp = travel(root, false);
        if (tmp > max_sum) {
            max_sum = tmp;
        }
        
        return max_sum;
    }
    // 遍历某个结点,获取它的左右子结点各种情况的和的最大值
    int travel(TreeNode* node, bool cur_state) {
        int tmp;
        // 计算左结点各种情况的最大值
        int max_left = 0;
        if (node->left) {
            // 不选中左子结点的情况
            tmp = travel(node->left, false);
            if (tmp > max_left) {
                max_left = tmp;
            }
            // 若当前结点未被选中,则考虑选中左子结点的情况
            if (!cur_state) {
                tmp = travel(node->left, true) + node->left->val;
                if (tmp > max_left) {
                    max_left = tmp;
                }
            }
        }
        // 计算右结点各种情况的最大值
        int max_right = 0;
        if (node->right) {
            // 不选中右子结点的情况
            tmp = travel(node->right, false);
            if (tmp > max_right) {
                max_right = tmp;
            }
            // 若当前结点未被选中,则考虑选中右子结点的情况
            if (!cur_state) {
                tmp = travel(node->right, true) + node->right->val;
                if (tmp > max_right) {
                    max_right = tmp;
                }
            }
        }
        // 返回左右子结点最大值之和
        return max_left + max_right;
    }
};