原題:
https://leetcode-cn.com/problems/house-robber-iii/submissions/
在上次打劫完一條街道之後和一圈房屋後,小偷又發現了一個新的可行竊的地區。這個地區只有一個入口,我們稱之爲“根”。 除了“根”之外,每棟房子有且只有一個“父“房子與之相連。一番偵察之後,聰明的小偷意識到“這個地方的所有房屋的排列類似於一棵二叉樹”。 如果兩個直接相連的房子在同一天晚上被打劫,房屋將自動報警。
計算在不觸動警報的情況下,小偷一晚能夠盜取的最高金額。
方法一
在看完這道題目後,第一反應這是一個動態規劃問題,我們定義process(TreeNode root)
返回以root
爲根節點的子樹的偷竊最高價值。
是否可以得到一個簡單的遞推關係process(root)=root.val+process(root.left)+process(root.right)
呢?根據題意,顯然是不可以的,如果偷竊了root
,則不能偷竊root.left
和root.right
。這樣,我們必須用一個變量來標識以root
爲根節點的子樹偷盜價值中是否包含root
的價值,這樣我們的遞歸函數變成了process(TreeNode root,boolean can)
,其中變量can
表示了root
節點是否被偷竊。於是得到了以下代碼
class Solution {
public int rob(TreeNode root) {
return process(root,true);
}
/*返回以root節點爲根節點的子樹的偷竊最大價值*/
public int process(TreeNode root,boolean can){
if(root==null)
return 0;
int result_1=0;
int result_2=0;
if(can==true){//偷竊該節點
result_1+=root.val;
result_1+=process(root.left,false);
result_1+=process(root.right,false);
}
//不偷竊該節點,偷竊左右節點
result_2+=process(root.left,true);
result_2+=process(root.right,true);
return Math.max(result_1,result_2);
}
}
每一步遞歸的含義是計算以root
爲根節點的子樹的偷竊最大價值。
對於遞歸的節點root
,如果可以在該節點偷盜,那麼對於以該節點爲根的子樹的偷竊最大值就有兩種情況組成,一種是偷竊root
節點,因此就不能偷竊左右子節點;另一種是不偷竊root
節點,偷竊其左右節點。比較這兩種情況中偷竊數額最大的一種然後返回給上一層遞歸程序。如果 root
節點不能偷竊,那麼就只有一種情況了。
雖然這麼寫最後通過了,但是耗時非常長
方法二
由於方法一耗時很長,我開始思考有沒有可以進行優化的地方。
在處理以root
爲根節點的子樹的偷盜價值時,方法一遍歷了兩次,一次can
變量賦爲false
,一次賦值爲true
,其實這兩種情況通過一個遞歸函數就能得到答案。
class Solution {
public int rob(TreeNode root) {
int[] res =core(root);
return Math.max(res[0],res[1]);
}
//res[0]爲不包括根節點的最大值,res[1]爲包括根節點的最大值
public int[] core(TreeNode root){
if(root==null)
return new int[]{0,0};
if(root.left==null&&root.right==null){
return new int[]{0,root.val};
}
int[] l=core(root.left);
int[] r=core(root.right);
int[] result=new int[2];
result[0]=Math.max(l[0],l[1])+Math.max(r[0],r[1]);
result[1]=root.val+l[0]+r[0];
return result;
}
}
通過返回一個數組int[ ]
,其中int[0]
表示不含root
價值且以root
爲根節點的盜竊最大值,int[1]
表示含有root
價值且以root
爲根節點的盜竊最大值,其思路與方法一其實是類似的,但是大大減少了遞歸的數量,提高了運算速度。