leetcode:337. 打家劫舍 III - 力扣(LeetCode)
题目
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
思路
父节点一定和子节点连着的,所以相邻的节点不可以都偷,只能隔着偷。
二叉树里面遍历有3种方式,我们要用哪种遍历顺序呢?
先看下是如何考虑的,跟之前一样,对于当前的节点i,有两种情况:
偷i:那么其左右孩子就不偷。
不偷i:考虑左右孩子(这里是“考虑”,不一定真的要偷)。
所以说当前的节点i在递推过程中需要之前的状态,也就是其左右孩子,也就是说我们需要后序遍历(左右中)。
之前打家劫舍的问题,我们最后求的是dp[nums.size()-1]也就是偷最后一个房子的最大金钱,那么这里按照后序遍历,可以把root根节点看作是最后的房子。
递归三部曲
(1)这里的dp设计为一个长度为2的数组,记录当前节点偷与不偷得到的最大金钱。
dp[0]表示不偷该节点所得到的的最大金钱
dp[1]表示偷该节点所得到的的最大金钱
这里不可以再用i去代表每一个状态的下标了,因为是二叉树,不是数组,二叉树有自己的遍历方式。所以定义如下,返回的vector就是cur节点对应的dp数组。
vector<int> robTree(TreeNode* cur)
(2)终止条件
在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回
这也相当于dp数组的初始化
if (cur == NULL) return vector<int>{0, 0};
(3) 后序遍历
通过递归左节点,得到左节点偷与不偷的金钱。
通过递归右节点,得到右节点偷与不偷的金钱。
// 下标0:不偷,下标1:偷
vector<int> left = robTree(cur->left); // 左
vector<int> right = robTree(cur->right); // 右
这里的left和right都是二维数组。
然后看当前节点,也就是中的处理逻辑:
跟之前一样,当前节点也是分两个情况,偷与不偷:
- 如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0];
- 如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);
那么cur节点的返回值就是{val2, val1}
最后把root传入递归参数,得到result数组,再取result[0]和result[1]的最大值即可。
#include <iostream>
#include <vector>
using namespace std;
struct TreeNode
{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution
{
public:
/**
* 对当前节点进行偷窃判断,返回一个包含两个元素的数组,
* 第一个元素表示不偷窃当前节点所能得到的最大金额,
* 第二个元素表示偷窃当前节点所能得到的最大金额。
*
* @param cur 当前节点指针
* @return 包含两个元素的数组,[不偷窃当前节点的最大金额, 偷窃当前节点的最大金额]
*/
vector<int> robTree(TreeNode *cur)
{
// 如果当前节点为空,返回两个0值
if (cur == nullptr)
return {0, 0};
// 递归处理左子树
vector<int> left = robTree(cur->left);
// 递归处理右子树
vector<int> right = robTree(cur->right);
// 计算不偷窃当前节点的情况下的最大金额
int val1 = cur->val + left[0] + right[0];
// 计算偷窃当前节点的情况下的最大金额
int val2 = max(left[0], left[1]) + max(right[0], right[1]);
// 返回两种情况下的最大金额数组
return {val2, val1};
}
/**
* 计算从根节点开始偷窃能得到的最大金额
*
* @param root 根节点指针
* @return 最大偷窃金额
*/
int rob(TreeNode *root)
{
// 获取根节点的偷窃判断结果
vector<int> result = robTree(root);
// 返回两种情况下的最大值
return max(result[0], result[1]);
}
};
总结
树形dp,dp数组的形式真的没想到还能这样子写,归根还是在于数组跟树的遍历区别,树的遍历一定是那3种遍历顺序的,你用下标i根本无法表示。