专栏首页算法码上来【每日算法Day 106】打家劫舍系列最后一弹,撑住你就赢了!

【每日算法Day 106】打家劫舍系列最后一弹,撑住你就赢了!

题目描述

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

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

示例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.

题解

这次是在一棵树上偷窃了,做法还是一样。对于结点 r 来说,我们还是分为偷和不偷两种情况。

如果偷的话,它的左右儿子就不能偷了,所以最大价值就是左儿子不偷的最大价值,加上右儿子不偷的最大价值,再加上 r 的价值。

而如果不偷的话,最大价值就是左儿子偷或不偷的最大价值,加上右儿子偷或不偷的最大价值。

因为需要用到儿子结点偷和不偷两个价值,所以需要在 dfs 时返回两个值,用来表示偷和不偷两个最大价值,具体实现时用 pair 来表示。

可能有人会用另一种实现方式,用 dfs0 表示不偷的最大价值,dfs1 表示偷的最大价值,然后两个函数互相调用。注意这样理论上是可行的,但是会产生很多重复计算,最终会超时。所以这种方法需要记忆化搜索,比较麻烦,需要用 map<TreeNode*, int> 来保存每个结点的答案。

代码

复杂实现方式(c++)

class Solution {
public:
    unordered_map<TreeNode*, int> dp0, dp1;

    int dfs0(TreeNode* root) {
        if (!root) return 0;
        if (dp0.find(root) != dp0.end()) return dp0[root];
        int res = 0;
        res += max(dfs0(root->left), dfs1(root->left));
        res += max(dfs0(root->right), dfs1(root->right));
        return dp0[root]=res;
    }

    int dfs1(TreeNode* root) {
        if (!root) return 0;
        if (dp1.find(root) != dp1.end()) return dp1[root];
        int res = root->val;
        res += dfs0(root->left);
        res += dfs0(root->right);
        return dp1[root]=res;
    }

    int rob(TreeNode* root) {
        if (!root) return 0;
        dp0.clear();
        dp1.clear();
        return max(dfs0(root), dfs1(root));
    }
};

精简实现方式(c++)

class Solution {
public:
    typedef pair<int, int> pii;

    pii dfs(TreeNode* root) {
        if (!root) return {0, 0};
        pii l = dfs(root->left);
        pii r = dfs(root->right);
        int r0 = max(l.first, l.second) + max(r.first, r.second);
        int r1 = l.first + r.first + root->val;
        return {r0, r1};
    }

    int rob(TreeNode* root) {
        pii res = dfs(root);
        return max(res.first, res.second);
    }
};

参考资料

[1]

LeetCode 337. 打家劫舍 III: https://leetcode-cn.com/problems/house-robber-iii/

[2]

【每日算法Day 104】偷电瓶的周某今天放出来了,还不赶紧做这道题防范一下!: https://godweiyang.com/2020/04/18/leetcode-198/

[3]

【每日算法Day 105】打家劫舍第二弹:看好你的电瓶车!: https://godweiyang.com/2020/04/19/leetcode-213/

本文分享自微信公众号 - 算法码上来(GodNLP),作者:godweiyang

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【每日算法Day 76】经典面试题:中序遍历的下一个元素,5大解法汇总!

    首先本题中的二叉树还是个二叉搜索树,也就是中序遍历是单调递增的,所以我们可以利用这个性质来简化查找过程。

    godweiyang
  • 每日算法系列【LeetCode 685】冗余连接 II

    在本问题中,有根树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。每一个节点只有一个父节点,除了根节点没有父节点。

    godweiyang
  • 【每日算法Day 96】腾讯面试题:合并两个有序数组

    给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

    godweiyang
  • 480. 二叉树的所有路径递归

    讲真我见到递归真的是害怕,也没办法讲,这也是参考的别人的答案,过两天再让我写我可能就写不出来了,这个看了看理解了一点点,就先放在这里吧,也许写的多了就懂了也不一...

    和蔼的zhxing
  • LeetCode 222. 完全二叉树的节点个数(二分查找)

    说明: 完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底...

    Michael阿明
  • 程序员面试金典 - 面试题 04.05. 合法二叉搜索树(中序遍历)

    Michael阿明
  • Linux 基础学习2

    郭楷丰
  • 014.Docker Harbor+Keepalived+LVS+共享存储高可用架构

    共享后端存储是一种比较标准的方案,将多个Harbor实例共享同一个后端存储,任何一个实例持久化到存储的镜像,都可被其他实例中读取。通过前置LB组件,如Keepa...

    木二
  • 配置SVN的hooks功能自动更新代码

    用过SVN的小伙伴们,都应该知道hooks(钩子)这个功能,的确日常企业内部测试环境经常会用过这个“特殊”的功能,今天来聊一聊SVN hooks

    民工哥
  • Linux工作目录切换命令

    心跳包

扫码关注云+社区

领取腾讯云代金券