Day39-动态规划,leetcode198,213,337
- 打家劫舍
- 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
- 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
- 思路
- 当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了
- 1.确定dp数组定义及下标的含义:dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
- 2.确定递推公式:决定dp[i]的因素就是第i房间偷还是不偷。如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,即:第i-1房一定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为dp[i-2] 加上第i房间偷到的钱。如果不偷第i房间,那么dp[i] = dp[i - 1],即考虑i-1房。然后dp[i]取最大值,即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
- 3.dp数组如何初始化:递推公式的基础就是dp[0] 和 dp[1]。从dp[i]的定义上来讲,dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1])。
- 4.确定遍历顺序:dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么从前到后遍历
- 5.举例推导dp数组,打印dp数组:
const rob = nums => {
// 数组长度
const len = nums.length;
// dp数组初始化
const dp = [nums[0], Math.max(nums[0], nums[1])];
// 从下标2开始遍历
for (let i = 2; i < len; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[len - 1];
};
- 打家劫舍 II
- 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
- 给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
- 思路
- 首尾相连组成环,考虑三种情况:1.考虑不包含首尾 2. 考虑包含首元素,不包含尾元素 3. 考虑包含尾元素,不包含首元素
- 情况2和情况3包含情况1
- 1.确定dp数组定义及下标的含义:dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
- 2.确定递推公式:决定dp[i]的因素就是第i房间偷还是不偷。如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,即:第i-1房一定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为dp[i-2] 加上第i房间偷到的钱。如果不偷第i房间,那么dp[i] = dp[i - 1],即考虑i-1房。然后dp[i]取最大值,即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
- 3.dp数组如何初始化:递推公式的基础就是dp[0] 和 dp[1]。从dp[i]的定义上来讲,dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1])。
- 4.确定遍历顺序:dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么从前到后遍历
- 5.举例推导dp数组,打印dp数组:
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) // 情况2
const result2 = robRange(nums, 1, n - 1) // 情况3
return Math.max(result1, result2)
};
// 打家劫舍198的情况
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]
}
- 打家劫舍 III
- 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
- 除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
- 给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
- 思路
- 每个节点有两个状态,偷和不偷
- 需要后序遍历,因为通过递归函数的返回值来做下一步计算
- 递归三部曲为框架,融合动规五部曲
- 1.确定递归函数的参数和返回值:一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组。参数为当前节点,返回数组就是dp数组。dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。在递归的过程中,系统栈会保存每一层递归的参数。
- 2.确定终止条件。在遍历的过程中,如果遇到空节点,无论偷还是不偷都是0,所以就返回
- 3.确定遍历顺序:首先明确的是使用后序遍历。 因为要通过递归函数的返回值来做下一步计算。通过递归左节点,得到左节点偷与不偷的金钱。通过递归右节点,得到右节点偷与不偷的金钱。
- 4.确定单层递归逻辑:如果是偷当前节点,那么左右孩子就不能偷,如果不偷当前节点,左右孩子就可以偷,选一个最大的。最后当前节点的状态是
- 5.举例推导dp数组
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);
};
参考&感谢各路大神
宝剑锋从磨砺出,梅花香自苦寒来。

浙公网安备 33010602011771号