题目来源于leetcode,解法和思路仅代表个人观点。传送门。
难度:中等
用时:03:00:00(大概3小时吧,思路有点bug)
题目
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 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.
思路
乍一看还以为是广度遍历的题目,仔细一看,没这么简单。
分析一下,每个节点都有拿与不拿的状态,该节点的状态,又影响之后节点的状态。
图中可以看出,如果该节点拿有一种情况,不拿有4总情况。那么我们我可以把该节点拿与不拿的情况遍历出来之后存到数组里面。
如果拿该节点,那么不拿其左节点和右节点。
如果不拿该节点,那么其左节点和右节点,拿和不拿都有可能。
设dp[n][m]为节点n,状态为m时,的最大值。其中m=0表示不拿,m=1表示拿。
d
p
[
n
]
[
m
]
=
{
n
o
d
e
.
v
a
l
+
d
p
[
n
o
d
e
.
l
e
f
t
]
[
0
]
+
d
p
[
n
o
d
e
.
r
i
g
h
t
]
[
0
]
,
m
=
1
m
a
x
{
d
p
[
n
o
d
e
.
l
e
f
t
]
[
0
]
,
d
p
[
n
o
d
e
.
l
e
f
t
]
[
1
]
}
+
m
a
x
{
d
p
[
n
o
d
e
.
r
i
g
h
t
]
[
0
]
,
d
p
[
n
o
d
e
.
r
i
g
h
t
]
[
1
]
}
,
m
=
0
dp[n][m]=\begin{cases} node.val + dp[node.left][0] + dp[node.right][0] &,& {m = 1}\\ max\{dp[node.left][0],dp[node.left][1]\}+max\{dp[node.right][0],dp[node.right][1]\} &,&{m=0} \end{cases}
dp[n][m]={node.val+dp[node.left][0]+dp[node.right][0]max{dp[node.left][0],dp[node.left][1]}+max{dp[node.right][0],dp[node.right][1]},,m=1m=0
该方法不是最优法,但是思想类似。后面附带官方版本。
代码
//my version
class Solution {
//map记忆储存每个节点的拿与不拿的状态
Map<Integer,int[]> map = new HashMap<>();
public int rob(TreeNode root) {
int ans = 0;
//1表示拿,0表示不拿
ans = Math.max(dfs(root,1,1),dfs(root,1,0));
return ans;
}
public int dfs(TreeNode node,int id,int status){
//如果该节点为空,就返回0
if(node == null){
return 0;
}
//如果之前已经存有该值
if(map.get(id)!=null){
int result = map.get(id)[status];
//如果这个状态存有值
if(result!=0){
return result;
}
}
//记录当前节点status状态的值
int sum = 0;
if(status == 1){
sum = node.val + dfs(node.left,2*id,0) + dfs(node.right,2*id+1,0);
}else if(status == 0){
/*
int temp1 = dfs(node.left,2*id,1) + dfs(node.right,2*id+1,1);
int temp2 = dfs(node.left,2*id,1) + dfs(node.right,2*id+1,0);
int temp3 = dfs(node.left,2*id,0) + dfs(node.right,2*id+1,1);
int temp4 = dfs(node.left,2*id,0) + dfs(node.right,2*id+1,0);
//取四个当中的最大值
sum = Math.max(Math.max(temp1,temp2),Math.max(temp3,temp4));
*/
//左节点(拿与不拿)的最大值+右节点(拿与不拿)的最大值
int temp1 = Math.max(dfs(node.left,2*id,0),dfs(node.left,2*id,1));
int temp2 = Math.max(dfs(node.right,2*id+1,0),dfs(node.right,2*id+1,1));
sum = temp1 + temp2;
}
int[] list;
//如果是第一次访问
if(map.get(id)==null) {
//把该节点加入到map中
list = new int[2];
}
//如果不是第一次访问
else {
list = map.get(id);
}
list[status] = sum;
map.put(id,list);
return sum;
}
}
//official version
class Solution {
public int rob(TreeNode root) {
int[] rootStatus = dfs(root);
return Math.max(rootStatus[0], rootStatus[1]);
}
public int[] dfs(TreeNode node) {
if (node == null) {
return new int[]{0, 0};
}
int[] l = dfs(node.left);
int[] r = dfs(node.right);
int selected = node.val + l[1] + r[1];
int notSelected = Math.max(l[0], l[1]) + Math.max(r[0], r[1]);
return new int[]{selected, notSelected};
}
}
算法复杂度
时间复杂度: O(n)。每个节点的拿与不拿只遍历一次。
空间复杂度: O(n)。存储每个节点拿与不拿的值。
官方版本快5ms