题目
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 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.
解题
方法一:动态规划
每个节点有两个状态 1.偷该节点 2.不偷该节点, 用一个长度为2的数组表示
要使用后续遍历,这样才可以通过左右节点的返回值,去做下一步的计算。
if(!cur) return vector{0,0};
就可以作为初始值
class Solution { public: int rob(TreeNode* root) { vector<int> res=robTree(root); return max(res[0],res[1]); } // 长度为2的数组,0:不偷,1:偷 vector<int> robTree(TreeNode* cur){ if(!cur) return vector<int>{0,0}; vector<int> left=robTree(cur->left); vector<int> right=robTree(cur->right); int val1=max(left[0],left[1])+max(right[0],right[1]);//不偷cur int val2=cur->val+left[0]+right[0];//偷cur return {val1,val2}; } };
将vector换成array后效率更高
class Solution { public: int rob(TreeNode* root) { array<int,2> res=robTree(root); return max(res[0],res[1]); } // 长度为2的数组,0:不偷,1:偷 array<int,2> robTree(TreeNode* cur){ if(!cur) return {0,0}; array<int,2> left=robTree(cur->left); array<int,2> right=robTree(cur->right); int val1=max(left[0],left[1])+max(right[0],right[1]);//不偷cur int val2=cur->val+left[0]+right[0];//偷cur return {val1,val2}; } };
java
class Solution { public int rob(TreeNode root) { int[] res=robTree(root); return Math.max(res[0],res[1]); } int[] robTree(TreeNode cur){ if(cur==null) return new int[]{0,0}; int[] left=robTree(cur.left); int[] right=robTree(cur.right); int val1=Math.max(left[0],left[1])+Math.max(right[0],right[1]); int val2=cur.val+left[0]+right[0]; return new int[]{val1,val2}; } }
方法二:暴力递归(超时)
暴力递归,可以发现,当前根节点,调用了
class Solution { public: int rob(TreeNode* root) { if(!root) return 0; if(!root->left&&!root->right) return root->val; int val1=root->val;//偷当前节点 if(root->left) val1+=rob(root->left->left)+rob(root->left->right); if(root->right) val1+=rob(root->right->left)+rob(root->right->right); int val2=rob(root->left)+rob(root->right);//不偷当前节点 return max(val1,val2); } };
当然以上代码超时了,这个递归的过程中其实是有重复计算了。
我们计算了root的四个孙子(左右孩子的孩子)为头结点的子树的情况,又计算了root的左右孩子为头结点的子树的情况,计算左右孩子的时候其实又把孙子计算了一遍。
方法三:记忆化递归
在方法二的基础上,使用unordered_map记录访问过的root,避免重复访问,减少时间复杂度
class Solution { public: unordered_map<TreeNode*,int> umap;//添加的地方 int rob(TreeNode* root) { if(!root) return 0; if(!root->left&&!root->right) return root->val; if(umap[root]) return umap[root];//添加的地方 int val1=root->val; if(root->left) val1+=rob(root->left->left)+rob(root->left->right); if(root->right) val1+=rob(root->right->left)+rob(root->right->right); int val2=rob(root->left)+rob(root->right); umap[root]=max(val1,val2);//添加的地方 return max(val1,val2); } };