337. 打家劫舍 III (House Robber III)

3,828 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

337. 打家劫舍 III 题目描述:小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 rootroot。除了 rootroot 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。给定二叉树的 root。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。

图示示例
image.png输入: root=[3,2,3,null,3,null,1]root = [3,2,3,null,3,null,1]
输出: 77
解释: 小偷一晚能够盗取的最高金额 3+3+1=73 + 3 + 1 = 7
图示示例
image.png输入: root=[3,4,5,1,3,null,1]root = [3,4,5,1,3,null,1]
输出: 99
解释: 小偷一晚能够盗取的最高金额 4+5=94 + 5 = 9

中规中矩的动态规划

每个节点依然有两个状态,选择 或者 不偷

1、定义 dp 状态数组

故用一个长度为 2 的一维数组 dpdp 来表示(dpdp 可能会用多个),其中

  • dp[0]dp[0] 存当前节点状态为 不偷 时的最大金额

  • dp[1]dp[1] 存当前节点状态为 时的最大金额

2、定义 dp 状态转移方程

  • 当前节点选择 不偷:当前节点能偷到的最大钱数 = 左孩子能偷到最多的钱 + 右孩子能偷到最多的钱,即,dpcurr[0]=max(dpleft[0]+dpleft[1])+max(dpright[0]+dpright[1])dp_{curr}[0] = max(dp_{left}[0] + dp_{left}[1]) + max(dp_{right}[0] + dp_{right}[1])

  • 当前节点选择 :当前节点能偷到的最大钱数 = 左孩子选择 不偷 时能得到的钱 + 右孩子选择 不偷 时能得到的钱 + 当前节点的钱数,即,dpcurr[1]=dpleft[0]+dpright[0]+curr.valdp_{curr}[1] = dp_{left}[0] + dp_{right}[0]+curr.val

3、定义 dp 初始状态

叶子节点(既没有 leftleft 孩子 也没有 rightright 孩子)才能定义初始状态,即 dpleaf=[0,leaf.val]dp_{leaf} = [0, leaf.val]

4、确定遍历顺序

  • 法1:从 rootroot 节点开始前序递归

  • 法2:将节点按照层序遍历的方式压入栈中,逐一出栈后计算其 dpdp

5、确定最终返回值

max(dproot[0],dproot[1])max(dp_{root}[0],dp_{root}[1])

6、代码示例

法1(递归版)

/*
 * 空间复杂度:O(n*n)
 * 时间复杂度:O(n)
 * @note n是节点个数,每个节点都需要遍历,同时额外开销一个固定长度的dp数组, 
 * 递归压栈还需要最多开销n个空间。
 */
function rob(root: TreeNode | null): number {
    const result = robHelper(root);
    return Math.max(result[0], result[1]);
};

function robHelper(currNode: TreeNode | null): number[] {
    const currDp = [0, 0];

    if(!currNode) return currDp;

    const leftDp = robHelper(currNode.left);
    const rightDp = robHelper(currNode.right);

    currDp[0] = Math.max(...leftDp) + Math.max(...rightDp);
    currDp[1] = leftDp[0] + rightDp[0] + currNode.val;

    return currDp;
}

法2(迭代版)

**NOTE: 使用 typescripttypescript 语言时,不能直接给对象添加动态属性(如:node.dp),需要借助 Reflect.set/get/has 三个函数才能支持自定义属性的添加、获取与检查。

/*
 * 空间复杂度:O(n)
 * 时间复杂度:O(n)
 */
function rob(root: TreeNode | null): number {
    const nodeQueue = transformIntoArrayByLevelOrdered(root);

    while(nodeQueue.length) {
        const currNode = nodeQueue.pop();

        const leftNodeDp = obtainValueFromObject(currNode.left, 'dp', [0, 0]);
        const rightNodeDp = obtainValueFromObject(currNode.right, 'dp', [0, 0]);

        const currNodeDp = obtainValueFromObject(currNode, 'dp', [0, 0]);
        currNodeDp[0] = Math.max(...leftNodeDp) + Math.max(...rightNodeDp);
        currNodeDp[1] = leftNodeDp[0] + rightNodeDp[0] + currNode.val;
    }

    const rootNodeDp = obtainValueFromObject(root, 'dp', [0, 0]);
    return Math.max(...rootNodeDp);
};

/**
 * 将二叉树压缩按层序遍历的方式压入栈中,并将每个节点挂上一个dp属性
 */
function transformIntoArrayByLevelOrdered(startNode: TreeNode | null) : TreeNode[] {
    if (!startNode) [];

    const startNodeDp = [0, isLeafNode(startNode) ? startNode.val : 0];
    defineNewPropToObject(startNode, 'dp', startNodeDp);
    const queueForCount = [startNode];
    const queueForStore = [startNode];

    while(queueForCount.length) {
        const currNode = queueForCount.shift();

        const leftNode = currNode.left, rightNode = currNode.right;
        if (leftNode) {
            const leftNodeDp = [0, isLeafNode(leftNode) ? leftNode.val : 0];
            defineNewPropToObject(leftNode, 'dp', leftNodeDp);
            queueForCount.push(leftNode);
            queueForStore.push(leftNode);
        }

        if (rightNode) {
            const rightNodeDp = [0, isLeafNode(rightNode) ? rightNode.val : 0];
            defineNewPropToObject(rightNode, 'dp', rightNodeDp);
            queueForCount.push(rightNode);
            queueForStore.push(rightNode);
        }
    }

    return queueForStore
}

/**
 * 既没有左子树,也没有右子树的节点就是叶子节点。
 */
function isLeafNode(node: TreeNode): boolean {
    return !node.left && !node.right;
}

function defineNewPropToObject<T>(obj: any, key: string, value: T) : boolean {
    return Reflect.set(obj, key, value);
}

function obtainValueFromObject<T>(obj: any, key: string, fallback: T): T {
    if (!!obj && Reflect.has(obj, key)) {
        return Reflect.get(obj, key);
    }

    return fallback;
}