1. 基础打家劫舍问题
-
dp数组的含义:dp[i]表示 考虑包括下标为i的房屋,最多可以偷盗的金额
-
递推公式:考虑两种情况,一种是当前房屋偷盗,则是
dp[i-2] + nums[i], 另一种情况是当前房屋不偷,则考虑dp[i-1](可能偷可能不偷), 最终递推公式为dp[i] = max((dp[i-2] + nums[i]), dp[i-1]) -
遍历顺序:从前到后
-
初始化:
dp[0] = nums[0], dp[1] = max(nums[0], nums[1]) -
dp数组举例
2. 成环的打家劫舍问题
题目: 213. 打家劫舍 II
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
教程 : 代码随想录 (programmercarl.com)
考虑三种情况,1. 考虑不包含首尾两个元素,2. 考虑不包含头元素,包含尾元素,3.考虑包含头元素,不包含尾元素。
注意我这里用的是"考虑" ,例如情况三,虽然是考虑包含尾元素,但不一定要选尾部元素!
而情况二 和 情况三 都包含了情况一了,所以只考虑情况二和情况三就可以了。
代码
var rob = function(nums) {
const n = nums.length
if (n === 0) return 0
if (n === 1) return nums[0]
const result1 = robRange(nums, 0, n - 2)
const result2 = robRange(nums, 1, n - 1)
return Math.max(result1, result2)
};
const robRange = (nums, start, end) => {
if (end === start) return nums[start]
const dp = Array(nums.length).fill(0)
dp[start] = nums[start]
dp[start + 1] = Math.max(nums[start], nums[start + 1])
for (let i = start + 2; i <= end; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1])
}
return dp[end]
}
3. 树形打家劫舍问题
1. 题目
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为
root。除了
root之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。给定二叉树的
root。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
2. 分析
树形打家劫舍问题是将递归问题和动态规划结合在了一起,因此要同时考虑递归的步骤和动态规划的步骤。
-
确定递归函数的参数和返回值
这里我们要求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组。其实这里的返回数组就是dp数组。
所以dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
-
确定终止条件
在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回
这也相当于dp数组的初始化
-
确定遍历顺序
首先明确的是使用后序遍历。 因为要通过递归函数的返回值来做下一步计算。
通过递归左节点,得到左节点偷与不偷的金钱。
通过递归右节点,得到右节点偷与不偷的金钱。
-
确定单层递归的逻辑
如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0]; (如果对下标含义不理解就再回顾一下dp数组的含义)
如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);
最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}
-
举例推导dp数组
3. 代码
const rob = root => {
// 后序遍历函数
const postOrder = node => {
// 递归出口
if (!node) return [0, 0];
// 遍历左子树
const left = postOrder(node.left);
// 遍历右子树
const right = postOrder(node.right);
// 不偷当前节点,左右子节点都可以偷或不偷,取最大值
const DoNot = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
// 偷当前节点,左右子节点只能不偷
const Do = node.val + left[0] + right[0];
// [不偷,偷]
return [DoNot, Do];
};
const res = postOrder(root);
// 返回最大值
return Math.max(...res);
};