在上次打劫完一條街道之後和一圈房屋後,小偷又發現了一個新的可行竊的地區。這個地區隻有一個入口,我們稱之為“根”。 除了“根”之外,每棟房子有且隻有一個“父“房子與之相連。一番偵察之後,聰明的小偷意識到“這個地方的所有房屋的排列類似于一棵二叉樹”。 如果兩個直接相連的房子在同一天晚上被打劫,房屋将自動報警。
計算在不觸動警報的情況下,小偷一晚能夠盜取的最高金額。
輸入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
輸出: 7
解釋: 小偷一晚能夠盜取的最高金額 = 3 + 3 + 1 = 7
回憶一下,在打家劫舍1中,我們一維數組dp[i] 來代表以第i個房子結尾時小偷能偷的最多的錢。這裡換個思路,對于每一個節點,可以選擇偷或者不偷,兩種不同的情況:我們用 f[i] 代表 i 節點被選擇時 i 的子樹可以偷到的最大金額;用 g[i] 代表 i 節點沒有被選擇時 i 的子樹可以偷到的最大金額。
大佬這麼解釋:
在設計狀态的時候,在後面加一維,消除後效性,就是二維數組,dp[i][j]——j=0就是不偷,j=1就是偷
但是之前的dp都是用數組來存儲,這裡樹是非線性的。是以這裡我選的資料結構是哈希表,map<TreeNode*,int>
再來思考他們的動态轉移方程:
- f[i] = g[i->left]+g[i->right]
- g[i] = max(f[i->left]+i->left->val,g[i->left]) + max(f[i->right]+i->right->val,g[i->right])
第一個方程很好了解,既然選擇了目前節點,他的兒子們都不能選
對于第二個方程的解釋如下:
目前節點不能選,是以他的兒子都可以選,并且左兒子和右兒子是互不幹擾的是以首先可以确定的是,目前的結果就是左邊+右邊
再來具體看某一個兒子(假設是 r),這裡和打家劫舍1中一樣的想法,r可以選擇偷,也可以不偷,設想這種情況:r是1塊錢,但是r的兒子有1億,是以我們就需要作出選擇,從偷r(f[i->left]+i->left->val)和不偷r(g[i->left])中挑出最大值就可以了。
在寫代碼的時候要注意空指針就可以了
ac代碼如下:
class Solution {
public:
void dfs(TreeNode* root,map<TreeNode*,int>& f,map<TreeNode*,int>& g){
if(root==NULL) return;
dfs(root->left,f,g);
dfs(root->right,f,g);
f[root] = g[root->left]+g[root->right];
int l = 0,r =0;
if(root->left!=NULL)
l = max(f[root->left]+root->left->val,g[root->left]);
else l = 0;
if(root->right!=NULL)
r = max(f[root->right]+root->right->val,g[root->right]);
else r = 0;
g[root] = l + r;
return;
}
int rob(TreeNode* root) {
if(root==NULL) return 0;
map<TreeNode*,int> f; //yes
map<TreeNode*,int> g; //no
dfs(root,f,g);
return max(f[root]+root->val,g[root]);
}
};