力扣337. 打家劫舍 III(树形动态规划)
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1: 示例 2:
树形动态规划:
有两点思路需要重点注意:
- 在设计状态的时候,在后面加一维,消除后效性,打家劫舍第 1 题,股票系列问题 6 个问题,都是这样的思路;
- 树的问题,很多时候采用后序遍历:我们的逻辑是子结点陆续汇报信息给父结点,一层一层向上汇报,最后在根结点汇总值。
每个节点可选择偷或者不偷两种状态,根据题目意思,相连节点不能一起偷
- 当前节点选择偷时,那么两个孩子节点就不能选择偷了
- 当前节点选择不偷时,两个孩子节点只需要拿最多的钱出来就行(两个孩子节点偷不偷没关系)
我们使用一个大小为 2 的数组来表示 int[] res = new int[2] 0 代表不偷,1 代表偷
任何一个节点能偷到的最大钱的状态可以定义为:
- 当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
- 当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
关键:当前结点「偷」或者「不偷」决定了孩子结点偷或者不偷,把这一点设计成状态,放在第 2 维,这一步叫「消除后效性」,这一点技巧非常常见
//
// main.cpp
// rob3
//
// Created by MXQ on 2020/10/20.
//
#include <iostream>
#include<vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
int rob(TreeNode* root) {
vector<int> result(2,0);
robnode(root,result);
return max(result[0], result[1]);
}
void robnode(TreeNode* root,vector<int> &result)
{
if (root==nullptr) {
return;
}
//遍历一次
vector<int> leftresult(2,0);
vector<int> rightresult(2,0);
robnode(root->left,leftresult);
robnode(root->right,rightresult);
result[0]=max(leftresult[0],leftresult[1])+max(rightresult[0],rightresult[1]);
result[1]=leftresult[0]+rightresult[0]+root->val;
}
};
int main(int argc, const char * argv[]) {
// insert code here...
TreeNode p[6] = {3,4,5,1,3,1};
p[0].left=&p[1];p[0].right=&p[2];
p[1].left=&p[3];p[1].right=&p[4];
p[2].right=&p[5];
Solution s;
auto result=s.rob(p);
std::cout <<result<<endl;
std::cout << "Hello, World!\n";
return 0;
}