leetcode337. 打家劫舍 III(树状dp)

 

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

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

输入: [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

再来思考他们的动态转移方程:

  1. f[i] = g[i->left]+g[i->right]
  2. 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& f,map& 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 f; //yes
        map g; //no
        dfs(root,f,g);
        return max(f[root]+root->val,g[root]);
    }
};

 

你可能感兴趣的:(算法数据结构,树状dp)