LeetCode337.打家劫舍 III

131 阅读2分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战

题目

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

337.打家劫舍III

解题思路

递归做法

打不打劫根节点的选择,会影响后续的打劫行为,分为以下两种情况:

  • 打劫根节点,则不能打劫左右子节点,但是能打劫左右子节点的四个子树(如果有)。
  • 不打劫根节点,则能打劫左子节点和右子节点,收益是打劫左右子树的收益之和。
var helper = function(root) {
    if (root == null) return 0;
    // 打劫根节点
    let roothelper = root.val;
    if(root.left) {
        roothelper += helper(root.left.left) + helper(root.left.right);
    }
    if(root.right) {
        roothelper += helper(root.right.left) + helper(root.right.right);
    }
    let rootNohelper = helper(root.left) + helper(root.right);
    return Math.max(rootNohelper, roothelper);
};

上面的递归做法是暴力做法,存在很多重复计算的行为,会导致很长的时间消耗。我们计算了 root 的四个孙子子树,又计算了 root 的左右子树,而后者会把 root 的孙子子树重复计算一遍。 我们把计算过的结果存到 map。下次遇到相同的子问题时直接拿过来用,不用做重复的计算。

image.png

记忆化递归做法

为了避免上述的重复计算行为,我们可以使用一个map把计算过的结果保存一下,这样如果计算过孙子节点了,那么计算孩子的时候可以复用孙子节点的结果

// 记忆递归
var rob = function(root) {
    const record = new Map();
    var helper = function(root) {
        if (root == null) return 0;
        if(record.has(root)) return record.get(root);
        // 打劫根节点
        let rootRob = root.val;
        if(root.left) {
            rootRob += helper(root.left.left) + helper(root.left.right);
        }
        if(root.right) {
            rootRob += helper(root.right.left) + helper(root.right.right);
        }
        let rootNoRob = helper(root.left) + helper(root.right);
        let res = Math.max(rootNoRob, rootRob);
        record.set(root, res);
        return res;
    };
    return helper(root);
}