zoukankan      html  css  js  c++  java
  • LeetCode——337. 打家劫舍 III

    在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

    计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

    示例 1:

    输入: [3,2,3,null,3,null,1]
    
         3
        / 
       2   3
            
         3   1
    
    输出: 7 
    解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
    

    示例 2:

    输入: [3,4,5,1,3,null,1]
    
         3
        / 
       4   5
      /     
     1   3   1
    
    输出: 9
    解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
    

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

    递归

    这种问题是很典型的递归问题,可以利用回溯法来做,因为当前的计算需要依赖之前的结果,那么对于某一个节点,如果其左子节点存在,通过递归调用函数,算出不包含左子节点返回的值,同理,如果右子节点存在,算出不包含右子节点返回的值,

    那么此节点的最大值可能有两种情况,一种是该节点值加上不包含左子节点和右子节点的返回值之和,另一种是左右子节点返回值之和不包含当期节点值,取两者的较大值返回即可,但是这种方法无法通过 OJ,超时了,所以必须优化这种方法,

    这种方法重复计算了很多地方,比如要完成一个节点的计算,就得一直找左右子节点计算,可以把已经算过的节点用 HashMap 保存起来,以后递归调用的时候,现在 HashMap 里找,如果存在直接返回,如果不存在,等计算出来后,保存到 HashMap 中再返回,这样方便以后再调用,参见代码如下:

    c++

    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;
        }
    };
    

    java

    Map<TreeNode, Integer> memo = new HashMap<>();
    public int rob(TreeNode root) {
        if (root == null) return 0;
        // 利用备忘录消除重叠子问题
        if (memo.containsKey(root)) 
            return memo.get(root);
        // 抢,然后去下下家
        int do_it = root.val
            + (root.left == null ? 
                0 : rob(root.left.left) + rob(root.left.right))
            + (root.right == null ? 
                0 : rob(root.right.left) + rob(root.right.right));
        // 不抢,然后去下家
        int not_do = rob(root.left) + rob(root.right);
        
        int res = Math.max(do_it, not_do);
        memo.put(root, res);
        return res;
    }
    

    动态规划

    下面再来看一种方法,这种方法的递归函数返回一个大小为2的一维数组 res,

    其中 res[0] 表示不包含当前节点值的最大值,res[1] 表示包含当前值的最大值,

    那么在遍历某个节点时,首先对其左右子节点调用递归函数,分别得到包含与不包含左子节点值的最大值,和包含于不包含右子节点值的最大值,

    则当前节点的 res[0] 就是左子节点两种情况的较大值加上右子节点两种情况的较大值,res[1] 就是不包含左子节点值的最大值加上不包含右子节点值的最大值,和当前节点值之和,返回即可,参见代码如下:

    c++

    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;
        }
    };
    

    java

    class Solution {
        public int rob(TreeNode root) {
        	int[] res = dfs(root);
        	return Math.max(res[0], res[1]);
    	}
    
    	public int[] dfs(TreeNode root) {
        	if (root == null) return new int[2];
        	int[] res = new int[2];
    
        	int[] left = dfs(root.left);
        	int[] right = dfs(root.right);
    
        	res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        	res[1] = left[0] + right[0] + root.val;
    
        	return res;
    	}
    }
    

    动态规划改进

    这里的 helper 函数返回当前结点为根结点的最大 rob 的钱数,里面的两个参数l和r表示分别从左子结点和右子结点开始 rob,分别能获得的最大钱数。

    在递归函数里面,如果当前结点不存在,直接返回0。否则对左右子结点分别调用递归函数,得到l和r。

    另外还得到四个变量,ll和lr表示左子结点的左右子结点的最大 rob 钱数,rl 和 rr 表示右子结点的最大 rob 钱数。

    那么最后返回的值其实是两部分的值比较,其中一部分的值是当前的结点值加上 ll, lr, rl, 和 rr 这四个值,这不难理解,因为抢了当前的房屋,则左右两个子结点就不能再抢了,但是再下一层的四个子结点都是可以抢的;

    另一部分是不抢当前房屋,而是抢其左右两个子结点,即 l+r 的值,返回两个部分的值中的较大值即可,参见代码如下:

    c++

    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);
        }
    };
    
  • 相关阅读:
    Code Forces 650 C Table Compression(并查集)
    Code Forces 645B Mischievous Mess Makers
    POJ 3735 Training little cats(矩阵快速幂)
    POJ 3233 Matrix Power Series(矩阵快速幂)
    PAT 1026 Table Tennis (30)
    ZOJ 3609 Modular Inverse
    Java实现 LeetCode 746 使用最小花费爬楼梯(递推)
    Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
    Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
    Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
  • 原文地址:https://www.cnblogs.com/wwj99/p/12408518.html
Copyright © 2011-2022 走看看