[LeetCode] 337. House Robber III 打家劫舍之三


 

The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the "root." Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that "all houses in this place forms a binary tree". It will automatically contact the police if two directly-linked houses were broken into on the same night.

Determine the maximum amount of money the thief can rob tonight without alerting the police.

Example 1:

     3
    / \
   2   3
    \   \ 
     3   1

Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.

 

Example 2:

     3
    / \
   4   5
  / \   \ 
 1   3   1

Maximum amount of money the thief can rob = 4 + 5 = 9.

 

Credits:
Special thanks to @dietpepsi for adding this problem and creating all test cases.

 

這道題是之前那兩道 House Robber II 和 House Robber 的拓展,這個小偷又偷出新花樣了,沿着二叉樹開始偷,碉堡了,題目中給的例子看似好像是要每隔一個偷一次,但實際上不一定只隔一個,比如如下這個例子:

 

        4
       /
      1
     /
    2
   /
  3

 

如果隔一個偷,那么是 4+2=6,其實最優解應為 4+3=7,隔了兩個,所以說純粹是怎么多怎么來,那么這種問題是很典型的遞歸問題,可以利用回溯法來做,因為當前的計算需要依賴之前的結果,那么對於某一個節點,如果其左子節點存在,通過遞歸調用函數,算出不包含左子節點返回的值,同理,如果右子節點存在,算出不包含右子節點返回的值,那么此節點的最大值可能有兩種情況,一種是該節點值加上不包含左子節點和右子節點的返回值之和,另一種是左右子節點返回值之和不包含當期節點值,取兩者的較大值返回即可,但是這種方法無法通過 OJ,超時了,所以必須優化這種方法,這種方法重復計算了很多地方,比如要完成一個節點的計算,就得一直找左右子節點計算,可以把已經算過的節點用 HashMap 保存起來,以后遞歸調用的時候,現在 HashMap 里找,如果存在直接返回,如果不存在,等計算出來后,保存到 HashMap 中再返回,這樣方便以后再調用,參見代碼如下:

 

解法一:

class Solution {
public:
    int rob(TreeNode* root) {
        unordered_map<TreeNode*, int> m;
        return dfs(root, m);
    }
    int dfs(TreeNode *root, unordered_map<TreeNode*, int> &m) {
        if (!root) return 0;
        if (m.count(root)) return m[root];
        int val = 0;
        if (root->left) {
            val += dfs(root->left->left, m) + dfs(root->left->right, m);
        }
        if (root->right) {
            val += dfs(root->right->left, m) + dfs(root->right->right, m);
        }
        val = max(val + root->val, dfs(root->left, m) + dfs(root->right, m));
        m[root] = val;
        return val;
    }
};

 

下面再來看一種方法,這種方法的遞歸函數返回一個大小為2的一維數組 res,其中 res[0] 表示不包含當前節點值的最大值,res[1] 表示包含當前值的最大值,那么在遍歷某個節點時,首先對其左右子節點調用遞歸函數,分別得到包含與不包含左子節點值的最大值,和包含於不包含右子節點值的最大值,則當前節點的 res[0] 就是左子節點兩種情況的較大值加上右子節點兩種情況的較大值,res[1] 就是不包含左子節點值的最大值加上不包含右子節點值的最大值,和當前節點值之和,返回即可,參見代碼如下:

 

解法二:

class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> res = dfs(root);
        return max(res[0], res[1]);
    }
    vector<int> dfs(TreeNode *root) {
        if (!root) return vector<int>(2, 0);
        vector<int> left = dfs(root->left);
        vector<int> right = dfs(root->right);
        vector<int> res(2, 0);
        res[0] = max(left[0], left[1]) + max(right[0], right[1]);
        res[1] = left[0] + right[0] + root->val;
        return res;
    }
};

 

下面這種解法由網友 edyyy 提供,仔細看了一下,也非常的巧妙,思路和解法二有些類似。這里的 helper 函數返回當前結點為根結點的最大 rob 的錢數,里面的兩個參數l和r表示分別從左子結點和右子結點開始 rob,分別能獲得的最大錢數。在遞歸函數里面,如果當前結點不存在,直接返回0。否則對左右子結點分別調用遞歸函數,得到l和r。另外還得到四個變量,ll和lr表示左子結點的左右子結點的最大 rob 錢數,rl 和 rr 表示右子結點的最大 rob 錢數。那么最后返回的值其實是兩部分的值比較,其中一部分的值是當前的結點值加上 ll, lr, rl, 和 rr 這四個值,這不難理解,因為搶了當前的房屋,則左右兩個子結點就不能再搶了,但是再下一層的四個子結點都是可以搶的;另一部分是不搶當前房屋,而是搶其左右兩個子結點,即 l+r 的值,返回兩個部分的值中的較大值即可,參見代碼如下:

 

解法三:

class Solution {
public:
    int rob(TreeNode* root) {
        int l = 0, r = 0;
        return helper(root, l, r);
    }
    int helper(TreeNode* node, int& l, int& r) {
        if (!node) return 0;
        int ll = 0, lr = 0, rl = 0, rr = 0;
        l = helper(node->left, ll, lr);
        r = helper(node->right, rl, rr);
        return max(node->val + ll + lr + rl + rr, l + r);
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/337

 

類似題目:

House Robber II

House Robber

 

參考資料:

https://leetcode.com/problems/house-robber-iii/

https://leetcode.com/problems/house-robber-iii/discuss/79333/Simple-C%2B%2B-solution

https://leetcode.com/problems/house-robber-iii/discuss/79363/Easy-understanding-solution-with-dfs

https://leetcode.com/problems/house-robber-iii/discuss/79330/Step-by-step-tackling-of-the-problem

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM