The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the "root." Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that "all houses in this place forms a binary tree". It will automatically contact the police if two directly-linked houses were broken into on the same night.

Determine the maximum amount of money the thief can rob tonight without alerting the police.

Example 1:

     3
/ \
2 3
\ \
3 1

Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.

Example 2:

     3
/ \
4 5
/ \ \
1 3 1

Maximum amount of money the thief can rob = 4 + 5 = 9.

Credits:
Special thanks to @dietpepsi for adding this problem and creating all test cases.

这道题是之前那两道 House Robber II 和 House Robber 的拓展,这个小偷又偷出新花样了,沿着二叉树开始偷,碉堡了,题目中给的例子看似好像是要每隔一个偷一次,但实际上不一定只隔一个,比如如下这个例子:

       /

     /

   /
  

如果隔一个偷,那么是 4+2=6,其实最优解应为 4+3=7,隔了两个,所以说纯粹是怎么多怎么来,那么这种问题是很典型的递归问题,可以利用回溯法来做,因为当前的计算需要依赖之前的结果,那么对于某一个节点,如果其左子节点存在,通过递归调用函数,算出不包含左子节点返回的值,同理,如果右子节点存在,算出不包含右子节点返回的值,那么此节点的最大值可能有两种情况,一种是该节点值加上不包含左子节点和右子节点的返回值之和,另一种是左右子节点返回值之和不包含当期节点值,取两者的较大值返回即可,但是这种方法无法通过 OJ,超时了,所以必须优化这种方法,这种方法重复计算了很多地方,比如要完成一个节点的计算,就得一直找左右子节点计算,可以把已经算过的节点用 HashMap 保存起来,以后递归调用的时候,现在 HashMap 里找,如果存在直接返回,如果不存在,等计算出来后,保存到 HashMap 中再返回,这样方便以后再调用,参见代码如下:

解法一:

class Solution {
public:
int rob(TreeNode* root) {
unordered_map<TreeNode*, int> m;
return dfs(root, m);
}
int dfs(TreeNode *root, unordered_map<TreeNode*, int> &m) {
if (!root) return ;
if (m.count(root)) return m[root];
int val = ;
if (root->left) {
val += dfs(root->left->left, m) + dfs(root->left->right, m);
}
if (root->right) {
val += dfs(root->right->left, m) + dfs(root->right->right, m);
}
val = max(val + root->val, dfs(root->left, m) + dfs(root->right, m));
m[root] = val;
return val;
}
};

下面再来看一种方法,这种方法的递归函数返回一个大小为2的一维数组 res,其中 res[0] 表示不包含当前节点值的最大值,res[1] 表示包含当前值的最大值,那么在遍历某个节点时,首先对其左右子节点调用递归函数,分别得到包含与不包含左子节点值的最大值,和包含于不包含右子节点值的最大值,则当前节点的 res[0] 就是左子节点两种情况的较大值加上右子节点两种情况的较大值,res[1] 就是不包含左子节点值的最大值加上不包含右子节点值的最大值,和当前节点值之和,返回即可,参见代码如下:

解法二:

class Solution {
public:
int rob(TreeNode* root) {
vector<int> res = dfs(root);
return max(res[], res[]);
}
vector<int> dfs(TreeNode *root) {
if (!root) return vector<int>(, );
vector<int> left = dfs(root->left);
vector<int> right = dfs(root->right);
vector<int> res(, );
res[] = max(left[], left[]) + max(right[], right[]);
res[] = left[] + right[] + root->val;
return res;
}
};

下面这种解法由网友 edyyy 提供,仔细看了一下,也非常的巧妙,思路和解法二有些类似。这里的 helper 函数返回当前结点为根结点的最大 rob 的钱数,里面的两个参数l和r表示分别从左子结点和右子结点开始 rob,分别能获得的最大钱数。在递归函数里面,如果当前结点不存在,直接返回0。否则对左右子结点分别调用递归函数,得到l和r。另外还得到四个变量,ll和lr表示左子结点的左右子结点的最大 rob 钱数,rl 和 rr 表示右子结点的最大 rob 钱数。那么最后返回的值其实是两部分的值比较,其中一部分的值是当前的结点值加上 ll, lr, rl, 和 rr 这四个值,这不难理解,因为抢了当前的房屋,则左右两个子结点就不能再抢了,但是再下一层的四个子结点都是可以抢的;另一部分是不抢当前房屋,而是抢其左右两个子结点,即 l+r 的值,返回两个部分的值中的较大值即可,参见代码如下:

解法三:

class Solution {
public:
int rob(TreeNode* root) {
int l = , r = ;
return helper(root, l, r);
}
int helper(TreeNode* node, int& l, int& r) {
if (!node) return ;
int ll = , lr = , rl = , rr = ;
l = helper(node->left, ll, lr);
r = helper(node->right, rl, rr);
return max(node->val + ll + lr + rl + rr, l + r);
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/337

类似题目:

House Robber II

House Robber

参考资料:

https://leetcode.com/problems/house-robber-iii/

https://leetcode.com/problems/house-robber-iii/discuss/79333/Simple-C%2B%2B-solution

https://leetcode.com/problems/house-robber-iii/discuss/79363/Easy-understanding-solution-with-dfs

https://leetcode.com/problems/house-robber-iii/discuss/79330/Step-by-step-tackling-of-the-problem

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] 337. House Robber III 打家劫舍之三的更多相关文章

  1. [LeetCode] 337. House Robber III 打家劫舍 III

    The thief has found himself a new place for his thievery again. There is only one entrance to this a ...

  2. Leetcode 337. House Robber III

    337. House Robber III Total Accepted: 18475 Total Submissions: 47725 Difficulty: Medium The thief ha ...

  3. [LintCode] House Robber III 打家劫舍之三

    The thief has found himself a new place for his thievery again. There is only one entrance to this a ...

  4. [LeetCode] House Robber III 打家劫舍之三

    The thief has found himself a new place for his thievery again. There is only one entrance to this a ...

  5. Java [Leetcode 337]House Robber III

    题目描述: The thief has found himself a new place for his thievery again. There is only one entrance to ...

  6. 337 House Robber III 打家劫舍 III

    小偷又发现一个新的可行窃的地点. 这个地区只有一个入口,称为“根”. 除了根部之外,每栋房子有且只有一个父房子. 一番侦察之后,聪明的小偷意识到“这个地方的所有房屋形成了一棵二叉树”. 如果两个直接相 ...

  7. LeetCode 337. House Robber III 动态演示

    每个节点是个房间,数值代表钱.小偷偷里面的钱,不能偷连续的房间,至少要隔一个.问最多能偷多少钱 TreeNode* cur mp[{cur, true}]表示以cur为根的树,最多能偷的钱 mp[{c ...

  8. leetcode 198. House Robber 、 213. House Robber II 、337. House Robber III 、256. Paint House(lintcode 515) 、265. Paint House II(lintcode 516) 、276. Paint Fence(lintcode 514)

    House Robber:不能相邻,求能获得的最大值 House Robber II:不能相邻且第一个和最后一个不能同时取,求能获得的最大值 House Robber III:二叉树下的不能相邻,求能 ...

  9. 337. House Robber III(包含I和II)

    198. House Robber You are a professional robber planning to rob houses along a street. Each house ha ...

随机推荐

  1. 【shell脚本】定时备份数据库===dbbackup.sh

    定时备份数据库是很有必要的 一.脚本内容 [root@localhost dbbackup]# cat dbbackup.sh #!/bin/bash #备份数据库 mysqldump -uroot ...

  2. vue开发 element的select下拉框设定初值后,不能重新选择的问题

    问题描述: 用的element的select可多选的下拉选框,在回显后有初始值的情况下,不能修改,也不能再选择 如图,明明点击了一般内勤主管,但没有任何反应 <el-select v-model ...

  3. make 安装

    wget https://kojipkgs.fedoraproject.org//packages/make/4.2.1/14.fc31/src/make-4.2.1-14.fc31.src.rpm ...

  4. Oracle数据库rownum用法集锦

    Oracle中rownum可以用来限制查询 具体用法: 1.返回查询集合中的第1行 select * from tableName where rownum = 1 2.返回查询集合中的第2行 错误示 ...

  5. sql server 下载安装标记

    SQL Server 2017 的各版本和支持的功能 https://docs.microsoft.com/zh-cn/sql/sql-server/editions-and-components-o ...

  6. Kubernetes Pod 调度约束

    Kubernetes Pod 调度约束 可以将pod调度到指定的节点Node内 默认:根据节点资源利用率等分配Node节点. nodeName用于将Pod调度到指定的Node名称上 nodeSelec ...

  7. SAP-简单的OALV演示练习

    接上一篇传统ALV:https://www.cnblogs.com/BruceKing/p/11320165.html. 首先介绍下什么是ALV,在R/3 4.6C之前ALV全称为ABAP List ...

  8. Microsoft.Windows.Controls.Ribbon.RibbonWindow 碰到 AvalonDock出现的诡异现象

    部分一 14年底进入目前公司时,领导准备开发一款新软件平台以取代原有平台.原平台采用C++Build开发界面(window c/s客户端) .Visual Studio(封装dll模块).过完年,领导 ...

  9. Python - 获取本机IP地址、Mac地址

    Python - 获取本机IP地址.Mac地址 在python中获取ip地址和在php中有很大不同,在php中往往比较简单.那再python中怎么做呢? 直接看代码: # Python - 获取本机I ...

  10. OpenCV.Net基于傅里叶变换进行文本的旋转校正

    本文描述一种利用OpenCV及傅里叶变换识别图片中文本旋转角度并自动校正的方法,由于对C#比较熟,因此本文将使用OpenCVSharp. 文章参考了http://johnhany.net/2013/1 ...