首页 > 其他分享 >代码随想录——动态规划31打家劫舍III(树状DP)

代码随想录——动态规划31打家劫舍III(树状DP)

时间:2025-01-20 12:10:43浏览次数:1  
标签:right root 31 随想录 DP III 节点 dp left

这道题目是 打家劫舍 III(House Robber III),是打家劫舍系列问题的变种。问题描述如下:

小偷发现了一个新的区域,这个区域的所有房屋排列类似于一棵二叉树。如果两个直接相连的房屋在同一晚被打劫,房屋会自动报警。给定这棵二叉树的根节点 root,求在不触发警报的情况下,小偷能够盗取的最高金额。
image


题目思路

1. 问题分析

  • 每个节点(房屋)有两个状态:不偷
  • 如果偷了当前节点,则不能偷其直接子节点(左子节点和右子节点)。
  • 如果不偷当前节点,则可以偷其子节点,也可以选择不偷子节点。
  • 目标是找到一种偷窃方案,使得总金额最大。

2. 解题方法

这道题有两种常见的解法:

  1. 记忆化搜索 + 递归
  2. 树形动态规划(树形DP)

不能用层序遍历化为一维数组再套用前面的打家劫舍。如以下情况不能解决
image


方法一:记忆化搜索 + 递归

思路

  • 对于每个节点,有两种选择:
    1. 偷当前节点:不能偷其左右子节点,但可以偷其孙子节点(左右子节点的子节点)。
    2. 不偷当前节点:可以考虑偷其左右子节点。
  • 使用递归计算这两种选择的最大值。
  • 记忆化搜索:每次偷爷爷节点时,又要重新计算孙子节点。但在此之前孙子节点已经计算过了。为了避免重复计算,使用 unordered_map<TreeNode*, int> 进行记忆化存储。

之前记忆化搜索一般使用数组(如斐波那契数列)缓存。但二叉树不适合拿数组当缓存,我们这次使用哈希表来存储结果

代码实现

class Solution {
public:
    unordered_map<TreeNode*, int> mp; // 记忆化存储

    int rob(TreeNode* root) {
        if (root == nullptr) return 0; // 空节点,返回0
        if (mp[root]) return mp[root]; // 如果已经计算过,直接返回

        // 偷当前节点
        int val1 = root->val;
        if (root->left) val1 += rob(root->left->left) + rob(root->left->right); // 偷孙子节点
        if (root->right) val1 += rob(root->right->left) + rob(root->right->right); // 偷孙子节点

        // 不偷当前节点
        int val2 = rob(root->left) + rob(root->right); // 偷子节点

        mp[root] = max(val1, val2); // 记录当前节点的最大值
        return mp[root];
    }
};

复杂度分析

  • 时间复杂度O(n),其中 n 是节点数量。每个节点只会被计算一次。
  • 空间复杂度O(n),用于存储记忆化结果和递归栈。

方法二:树形动态规划(树形DP)

方法一还要考虑孙子节点,能不能只考虑儿子——>记录每个节点选/不选的状态最大值

思路

  • 对于每个节点,返回一个长度为 2 的数组 dp
    • dp[0]:不偷当前节点时的最大金额。
    • dp[1]:偷当前节点时的最大金额。
  • 通过后序遍历(左右根)计算每个节点的 dp 值。
  • 最终结果是根节点的 dp[0]dp[1] 的最大值。

代码实现

  • 树形DP在树上进行状态转移,因此需要递归实现
class Solution {
public:
    int rob(TreeNode* root) {
        vector<int> result = robTree(root);
        return max(result[0], result[1]); // 返回偷或不偷根节点的最大值
    }

    vector<int> robTree(TreeNode* root) {
        if (root == nullptr) return {0, 0}; // 空节点,返回 {0, 0}

        vector<int> left = robTree(root->left); // 左子树的 dp 值
        vector<int> right = robTree(root->right); // 右子树的 dp 值

        // 不偷当前节点:左右子树可偷可不偷
        int val0 = max(left[0], left[1]) + max(right[0], right[1]);
        // 偷当前节点:左右子树不可偷
        int val1 = root->val + left[0] + right[0];

        return {val0, val1}; // 返回当前节点的 dp 值
    }
};

复杂度分析

  • 时间复杂度O(n),每个节点只会被访问一次。
  • 空间复杂度O(n),递归栈的深度最大为树的高度。

方法对比

方法 优点 缺点
记忆化搜索 + 递归 直观,易于理解 需要额外的空间存储记忆化结果
树形DP 空间效率更高,无需额外存储 需要理解状态转移方程

总结

  • 记忆化搜索 + 递归:通过递归遍历树,同时使用 unordered_map 存储中间结果,避免重复计算。
  • 树形DP:通过后序遍历,返回每个节点的状态值(偷或不偷),最终选择最大值。

两种方法的时间复杂度都是 O(n),但树形DP的空间效率更高,推荐使用树形DP。

标签:right,root,31,随想录,DP,III,节点,dp,left
From: https://www.cnblogs.com/neromegumi/p/18681102

相关文章

  • [BZOJ3160] 万径人踪灭 题解
    首先正难则反,想到答案即为满足第一条要求的回文子序列数量,减去回文子串数量。回文子串数量\(hash+\)二分即可,考虑前半部分。假如我们将一个回文子序列一层层剥开,就会发现它其实是由多个相同的字母对拼成的。那么容易想到把字母\(a\)和字母\(b\)的贡献分开计算。那第一条要......
  • 代码随想录:二叉搜索时的插入
    /***Definitionforabinarytreenode.*structTreeNode{*intval;*TreeNode*left;*TreeNode*right;*TreeNode():val(0),left(nullptr),right(nullptr){}*TreeNode(intx):val(x),left(nullptr),right(nullptr){}*......
  • 代码随想录:将有序数组转化为二叉搜索树
    /***Definitionforabinarytreenode.*structTreeNode{*intval;*TreeNode*left;*TreeNode*right;*TreeNode():val(0),left(nullptr),right(nullptr){}*TreeNode(intx):val(x),left(nullptr),right(nullptr){}*......
  • 代码随想录:修剪二叉搜索树
    /***Definitionforabinarytreenode.*structTreeNode{*intval;*TreeNode*left;*TreeNode*right;*TreeNode():val(0),left(nullptr),right(nullptr){}*TreeNode(intx):val(x),left(nullptr),right(nullptr){}*......
  • 代码随想录:删除二叉搜索树中的节点
    由于涉及到树的结构变化,用递归写比较简单,竟然一次跑通了/***Definitionforabinarytreenode.*structTreeNode{*intval;*TreeNode*left;*TreeNode*right;*TreeNode():val(0),left(nullptr),right(nullptr){}*TreeNode(int......
  • 代码随想录:把二叉搜索树转化为累加树
    相当于将数组从右到左遍历,下一个数加上一个数,二叉搜索树中序遍历(左中右)为顺序,右中左则为倒叙/***Definitionforabinarytreenode.*structTreeNode{*intval;*TreeNode*left;*TreeNode*right;*TreeNode():val(0),left(nullptr),rig......
  • 31. 绘图技术
    一、QPainter绘图  绘图是指在绘图设备(窗口、控件、图像、打印机等)上将用户构思出的图形绘制出来,图形包括点、线、矩形、多边形、椭圆、文字及保存到磁盘上的图像等。可以对绘制的图形进行处理,如给封闭的图形填充颜色。  绘图设备是从QPaintDevice继承的类,包括继承自QWid......
  • VMware Avi Load Balancer 31.1.1 发布 - 多云负载均衡平台
    VMwareAviLoadBalancer31.1.1发布-多云负载均衡平台应用交付:多云负载均衡、Web应用防火墙和容器Ingress服务请访问原文链接:https://sysin.org/blog/vmware-avi-load-balancer-31/查看最新版。原创作品,转载请保留出处。作者主页:sysin.org负载均衡平台VMwareAviL......
  • 例题_树基础 P5318
    洛谷P5318分析关键词n篇文章m条参考文献引用关系x文章有y参考文献BFS&&DFS结果步骤定义不仅要定义关键词,还要再定义一个容器这里用\(set\)set<int>e[100009];注意要初始化输入输入nmxy这几个关键字计算过程分两步深搜广搜输出先调用函数,在......
  • 202312 青少年软件编程等级考试C/C++ 二级真题答案及解析(电子学会)
    第1题统计指定范围里的数给定一个数的序列S,以及一个区间[L,R],求序列中介于该区间的数的个数,即序列中大于等于L且小于等于R的数的个数。时间限制:1000内存限制:65536输入第一行1个整数n,表示序列的长度。(0<n≤10000) 第二行n个正整数,表示序列里的每一个数,每个数小于等......