LeetCode 337. 打家劫舍 III

题目描述

337. 打家劫舍 III

image-20250419000557407

image-20250419000609853

思路分析

解法一:树形 DP(后序遍历)(推荐)

核心思路

  • 本题是「打家劫舍」系列在二叉树上的变形,约束变为”相邻节点(父子关系)不能同时偷”。
  • 对每个节点,有”偷”和”不偷”两种选择,需要递归子树的结果来决策。
  • 朴素递归存在大量重复计算,优化方案是后序遍历时每个节点同时返回两个值:[rob, notRob]
  • rob 表示偷当前节点可获得的最大金额,notRob 表示不偷当前节点可获得的最大金额。
  • 状态转移:rob = node.val + left[1] + right[1](偷当前,子节点必须不偷);notRob = max(left[0], left[1]) + max(right[0], right[1])(不偷当前,子节点随意取最优)。
  • 最终答案为根节点 max(rob, notRob)


复杂度分析

  • 时间复杂度:O(n),其中 n 表示二叉树的节点数,每个节点恰好访问一次。
  • 空间复杂度:O(h),其中 h 表示二叉树的高度,递归栈深度最坏为 O(n),平均为 O(log n)。
class Solution {
    public int rob(TreeNode root) {
        int[] res = dfs(root);
        return Math.max(res[0], res[1]);
    }

    // 后序遍历,返回 [偷当前节点的最大值, 不偷当前节点的最大值]
    private int[] dfs(TreeNode node) {
        if (node == null) {
            return new int[]{0, 0};
        }
        // 递归计算左右子树的两种状态
        int[] left = dfs(node.left);
        int[] right = dfs(node.right);
        // 偷当前节点:子节点必须不偷
        int rob = node.val + left[1] + right[1];
        // 不偷当前节点:子节点可偷可不偷,取最大值
        int notRob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        return new int[]{rob, notRob};
    }
}
func rob(root *TreeNode) int {
	// dfs 返回两个状态:偷当前节点的最大值、不偷当前节点的最大值
	var dfs func(*TreeNode) (int, int)
	dfs = func(cur *TreeNode) (int, int) {
		if cur == nil {
			return 0, 0
		}
		// 递归计算左右子树的两种状态
		leftRob, leftNotRob := dfs(cur.Left)
		rightRob, rightNotRob := dfs(cur.Right)
		// 偷当前节点:左右子节点必须不偷
		robCur := cur.Val + leftNotRob + rightNotRob
		// 不偷当前节点:左右子节点各取最优
		notRobCur := max(leftRob, leftNotRob) + max(rightRob, rightNotRob)
		return robCur, notRobCur
	}

	resRob, resNotRob := dfs(root)
	return max(resRob, resNotRob)
}

相似题目

题目 难度 考察点
198. 打家劫舍 中等 动态规划
213. 打家劫舍 II 中等 动态规划、环形
740. 删除并获得点数 中等 动态规划
124. 二叉树中的最大路径和 困难 树形 DP、后序遍历
543. 二叉树的直径 简单 后序遍历两状态
968. 监控二叉树 困难 树形 DP 三状态
2246. 相邻字符不同的最长路径 困难 树形 DP、后序遍历
本文作者:
本文链接: https://hgnulb.github.io/blog/2025/99943604
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!