题目一 198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
思路
这个题目还比较简单,容易想出来。
- dp[i]: 偷窃前i个房间得到的最高金额
- 递推公式:
- 偷窃i,
dp\[i] = dp\[i - 2] + nums\[i] - 不偷窃i,
dp\[i] = dp\[i - 1] max(dp\[i-1], dp\[i - 2] + nums\[i])
- 偷窃i,
- 初始化:
dp\[0] = nums\[0]dp\[1] = max(nums\[0], nums\[1])
- 遍历顺序:依赖前面的值,从前往后遍历
/**
* dp[i]: 偷窃前i个房间得到的最高金额
* 递推公式:
* 偷窃i,dp[i] = dp[i - 2] + nums[i]
* 不偷窃i,dp[i] = dp[i - 1]
* 初始化:dp[0] = nums[0] dp[1] = max(nums[0], nums[1])
* @param {number[]} nums
* @return {number}
*/
var rob = function(nums) {
const len = nums.length;
const dp = new Array(len + 1).fill(0);
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
// 注意边界
for (let i = 2; i < len; i++) {
dp[i] = Math.max(dp[i-1], dp[i - 2] + nums[i]);
}
// 最后是取len-1
return dp[len - 1];
};
题目二 213. 打家劫舍 II
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
思路
在前一道题的基础上,加了环形的限制条件,分为2种情况:
- 包含一个元素,不包含最后一个元素,1 - len-1
- 不包含第一个元素,包含最后一个元素: 2-len
以上两种情况按照第一道题的递归来,包含某个元素不一定指选取这个元素,也会有选取和不选取的情况,最后取上面2种情况结果最大值。
/**
* dp[i]: 偷窃前i个房间得到的最高金额
* 考虑第1个:区间 1 - len-1
* 考虑最后一个:2-len
* 初始化:dp[0] = 0, dp[1] = nums[0]
* @param {number[]} nums
* @return {number}
*/
var rob = function(nums) {
const len = nums.length;
if (len === 1) {
return nums[0];
}
if (len === 2) {
return Math.max(nums[0], nums[1]);
}
var robInner = (nums, start, end) => {
const dp = new Array(end - start + 1).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-1], dp[i-2] + nums[i]);
}
return dp[end];
}
// 注意区间
const left = robInner(nums, 0, len - 2);
const right = robInner(nums, 1, len - 1);
return Math.max(left, right);
};
题目三 337. 打家劫舍 III
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
思路
增加难度:改为二叉树的结构,如果按照二叉树遍历的方式,写出来后结果直接超时。
dp的解法:维护两个值,代表不偷当前节点的最大值和偷当前节点的最大值, dp[0] 不偷,dp[1] 偷
- 遍历顺序:左右根,求出左右子树的dp数组
- 递推公式:
- 不偷该节点,则是左右子树结果的最大值的和,是取最大值,不是左右都偷的结果和。
dp\[0] = max(left\[0], left\[1]) + max(right\[0], right\[1]) - 偷该节点:当前值加上左右子树不偷的值
dp\[1] = root.val + left\[0] + right\[0]
- 不偷该节点,则是左右子树结果的最大值的和,是取最大值,不是左右都偷的结果和。
- 初始化: 非空返回0,一个节点,返回当前值。
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* 递归超时
* @param {TreeNode} root
* @return {number}
*/
var rob = function(root) {
if (!root) {
return 0;
}
if (!root.left && !root.right) {
return root.val;
}
// 选根节点
let 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);
}
// 不选根节点
const val2 = rob(root.left) + rob(root.right);
return Math.max(val1, val2);
};
/**
* dp: 维护两个值,偷当前节点的最大值和不偷当前节点的最大值, dp[0] 不偷,dp[1] 偷
* 遍历顺序:左右根,求出左右子树的dp数组
* 递推公式: dp[0] = max(left[0], left[1]) + max(right[0], right[1])
* dp[1] = root.val + left[0] + right[0]
* 初始化: 非空返回0,一个节点,返回当前值。
*/
var rob = function (root) {
if (!root) {
return 0;
}
var robInner = root => {
if (!root) {
return [0, 0];
}
if (!root.left && !root.right) {
return [0, root.val];
}
const left = robInner(root.left);
const right = robInner(root.right);
// 偷当前节点
const val1 = root.val + left[0] + right[0];
// 不偷当前节点
const val2 = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
return [val2, val1];
}
const result = robInner(root);
return Math.max(result[0], result[1])
}