代码随想录|动态规划|31打家劫舍III

leetcode:337. 打家劫舍 III - 力扣(LeetCode)

题目

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

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

337.打家劫舍III

思路

父节点一定和子节点连着的,所以相邻的节点不可以都偷,只能隔着偷。

二叉树里面遍历有3种方式,我们要用哪种遍历顺序呢?

先看下是如何考虑的,跟之前一样,对于当前的节点i,有两种情况:

偷i:那么其左右孩子就不偷。

不偷i:考虑左右孩子(这里是“考虑”,不一定真的要偷)。

所以说当前的节点i在递推过程中需要之前的状态,也就是其左右孩子,也就是说我们需要后序遍历(左右中)。

之前打家劫舍的问题,我们最后求的是dp[nums.size()-1]也就是偷最后一个房子的最大金钱,那么这里按照后序遍历,可以把root根节点看作是最后的房子。

递归三部曲

(1)这里的dp设计为一个长度为2的数组,记录当前节点偷与不偷得到的最大金钱。

dp[0]表示不偷该节点所得到的的最大金钱

dp[1]表示偷该节点所得到的的最大金钱

这里不可以再用i去代表每一个状态的下标了,因为是二叉树,不是数组,二叉树有自己的遍历方式。所以定义如下,返回的vector就是cur节点对应的dp数组。

vector<int> robTree(TreeNode* cur)

(2)终止条件

 在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回

这也相当于dp数组的初始化

if (cur == NULL) return vector<int>{0, 0};

(3) 后序遍历

通过递归左节点,得到左节点偷与不偷的金钱。

通过递归右节点,得到右节点偷与不偷的金钱。

// 下标0:不偷,下标1:偷
vector<int> left = robTree(cur->left); // 左
vector<int> right = robTree(cur->right); // 右

这里的left和right都是二维数组。

然后看当前节点,也就是中的处理逻辑:

跟之前一样,当前节点也是分两个情况,偷与不偷:

  • 如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0];
  • 如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);

那么cur节点的返回值就是{val2, val1}

最后把root传入递归参数,得到result数组,再取result[0]和result[1]的最大值即可。

#include <iostream>
#include <vector>
using namespace std;

struct TreeNode
{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

class Solution
{
public:
    /**
     * 对当前节点进行偷窃判断,返回一个包含两个元素的数组,
     * 第一个元素表示不偷窃当前节点所能得到的最大金额,
     * 第二个元素表示偷窃当前节点所能得到的最大金额。
     * 
     * @param cur 当前节点指针
     * @return 包含两个元素的数组,[不偷窃当前节点的最大金额, 偷窃当前节点的最大金额]
     */
    vector<int> robTree(TreeNode *cur)
    {
        // 如果当前节点为空,返回两个0值
        if (cur == nullptr)
            return {0, 0};
        
        // 递归处理左子树
        vector<int> left = robTree(cur->left);
        // 递归处理右子树
        vector<int> right = robTree(cur->right);
        
        // 计算不偷窃当前节点的情况下的最大金额
        int val1 = cur->val + left[0] + right[0];
        // 计算偷窃当前节点的情况下的最大金额
        int val2 = max(left[0], left[1]) + max(right[0], right[1]);
        
        // 返回两种情况下的最大金额数组
        return {val2, val1};
    }

    /**
     * 计算从根节点开始偷窃能得到的最大金额
     * 
     * @param root 根节点指针
     * @return 最大偷窃金额
     */
    int rob(TreeNode *root)
    {
        // 获取根节点的偷窃判断结果
        vector<int> result = robTree(root);
        // 返回两种情况下的最大值
        return max(result[0], result[1]);
    }
};

总结

树形dp,dp数组的形式真的没想到还能这样子写,归根还是在于数组跟树的遍历区别,树的遍历一定是那3种遍历顺序的,你用下标i根本无法表示。

参考资料

 代码随想录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值